diff --git a/deployments/local/docker-compose.yml b/deployments/local/docker-compose.yml index 54233431..c98e9d5a 100644 --- a/deployments/local/docker-compose.yml +++ b/deployments/local/docker-compose.yml @@ -369,6 +369,56 @@ services: - "traefik.http.services.chat-service.loadbalancer.sticky.cookie=true" - "traefik.http.services.chat-service.loadbalancer.sticky.cookie.name=chat_session" + # Social Service .NET - Social Features (Profiles, Posts, Followers) + social-service-net: + build: + context: ../../services/social-service-net + dockerfile: Dockerfile + image: goodgo/social-service-net:latest + container_name: social-service-net-local + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://+:8080 + # EN: Database - Neon PostgreSQL + # VI: Cơ sở dữ liệu - Neon PostgreSQL + - ConnectionStrings__DefaultConnection=Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Port=5432;Database=social_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require + # EN: IAM Service Communication + # VI: Giao tiếp IAM Service + - IamService__BaseUrl=http://iam-service-net:8080 + - IamService__ServiceName=social-service + # EN: JWT Configuration + # VI: Cấu hình JWT + - Jwt__Authority=http://iam-service-net:8080 + - Jwt__Audience=goodgo-api + - Jwt__RequireHttpsMetadata=false + # EN: Redis Cache + # VI: Cache Redis + - Redis__Host=167.114.174.113 + - Redis__Port=6379 + - Redis__Password=Velik@2026 + ports: + - "5009:8080" + depends_on: + iam-service-net: + condition: service_healthy + traefik: + condition: service_started + networks: + - microservices-network + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health/live"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + labels: + - "traefik.enable=true" + - "traefik.http.routers.social-service.rule=PathPrefix(`/api/v1/profiles`) || PathPrefix(`/api/v1/posts`) || PathPrefix(`/api/v1/followers`) || PathPrefix(`/api/v1/social`)" + - "traefik.http.routers.social-service.entrypoints=web" + - "traefik.http.services.social-service.loadbalancer.server.port=8080" + - "traefik.http.services.social-service.loadbalancer.healthcheck.path=/health/live" + - "traefik.http.services.social-service.loadbalancer.healthcheck.interval=10s" # Jaeger - Distributed Tracing diff --git a/services/chat-service-net/tests/ChatService.UnitTests/Application/Hubs/ClaimsUserIdProviderTests.cs b/services/chat-service-net/tests/ChatService.UnitTests/Application/Hubs/ClaimsUserIdProviderTests.cs new file mode 100644 index 00000000..10ac5147 --- /dev/null +++ b/services/chat-service-net/tests/ChatService.UnitTests/Application/Hubs/ClaimsUserIdProviderTests.cs @@ -0,0 +1,47 @@ +using System.Security.Claims; +using ChatService.API.Hubs; +using FluentAssertions; +using Microsoft.AspNetCore.SignalR; +using Xunit; + +namespace ChatService.UnitTests.Application.Hubs; + +/// +/// EN: Unit tests for ClaimsUserIdProvider. +/// VI: Unit tests cho ClaimsUserIdProvider. +/// +public class ClaimsUserIdProviderTests +{ + private readonly ClaimsUserIdProvider _sut; + + public ClaimsUserIdProviderTests() + { + _sut = new ClaimsUserIdProvider(); + } + + [Fact] + public void ClaimsUserIdProvider_ShouldImplementIUserIdProvider() + { + // Assert + _sut.Should().BeAssignableTo(); + } + + [Theory] + [InlineData(ClaimTypes.NameIdentifier)] + [InlineData("sub")] + [InlineData("user_id")] + public void GetUserId_ShouldSupportMultipleClaimTypes(string claimType) + { + // EN: This verifies the expected claim types are documented + // VI: Kiểm tra các claim types được hỗ trợ đã được ghi nhận + var supportedClaimTypes = new[] { ClaimTypes.NameIdentifier, "sub", "user_id" }; + supportedClaimTypes.Should().Contain(claimType); + } + + [Fact] + public void ClaimsUserIdProvider_ShouldNotBeNull() + { + // Assert + _sut.Should().NotBeNull(); + } +} diff --git a/services/chat-service-net/tests/ChatService.UnitTests/ChatService.UnitTests.csproj b/services/chat-service-net/tests/ChatService.UnitTests/ChatService.UnitTests.csproj index 37baced3..84501e96 100644 --- a/services/chat-service-net/tests/ChatService.UnitTests/ChatService.UnitTests.csproj +++ b/services/chat-service-net/tests/ChatService.UnitTests/ChatService.UnitTests.csproj @@ -29,6 +29,7 @@ + diff --git a/services/chat-service-net/tests/ChatService.UnitTests/Domain/Contracts/AIServiceContractTests.cs b/services/chat-service-net/tests/ChatService.UnitTests/Domain/Contracts/AIServiceContractTests.cs new file mode 100644 index 00000000..e8d7cdbe --- /dev/null +++ b/services/chat-service-net/tests/ChatService.UnitTests/Domain/Contracts/AIServiceContractTests.cs @@ -0,0 +1,87 @@ +using ChatService.Domain.Contracts; +using FluentAssertions; +using Xunit; + +namespace ChatService.UnitTests.Domain.Contracts; + +/// +/// EN: Unit tests for IAIService interface and related DTOs. +/// VI: Unit tests cho IAIService interface và các DTOs liên quan. +/// +public class AIServiceContractTests +{ + [Fact] + public void ChatMessage_ShouldCreateWithAllProperties() + { + // Arrange + var role = "user"; + var content = "Hello AI!"; + var timestamp = DateTime.UtcNow; + + // Act + var message = new ChatMessage(role, content, timestamp); + + // Assert + message.Role.Should().Be(role); + message.Content.Should().Be(content); + message.Timestamp.Should().Be(timestamp); + } + + [Theory] + [InlineData("user")] + [InlineData("assistant")] + [InlineData("system")] + public void ChatMessage_ShouldAcceptValidRoles(string role) + { + // Arrange & Act + var message = new ChatMessage(role, "Test content", DateTime.UtcNow); + + // Assert + message.Role.Should().Be(role); + } + + [Fact] + public void AIServiceOptions_ShouldHaveDefaultValues() + { + // Arrange & Act + var options = new AIServiceOptions(); + + // Assert + options.Provider.Should().Be("OpenAI"); + options.Model.Should().Be("gpt-4"); + options.MaxHistoryMessages.Should().Be(20); + options.MaxTokens.Should().Be(1000); + options.Temperature.Should().BeApproximately(0.7f, 0.001f); + options.SystemPrompt.Should().NotBeNullOrEmpty(); + } + + [Fact] + public void AIServiceOptions_SectionName_ShouldBeAI() + { + // Assert + AIServiceOptions.SectionName.Should().Be("AI"); + } + + [Fact] + public void AIServiceOptions_ShouldAllowCustomConfiguration() + { + // Arrange & Act + var options = new AIServiceOptions + { + Provider = "AzureOpenAI", + Model = "gpt-3.5-turbo", + MaxHistoryMessages = 50, + MaxTokens = 2000, + Temperature = 0.5f, + SystemPrompt = "Custom prompt" + }; + + // Assert + options.Provider.Should().Be("AzureOpenAI"); + options.Model.Should().Be("gpt-3.5-turbo"); + options.MaxHistoryMessages.Should().Be(50); + options.MaxTokens.Should().Be(2000); + options.Temperature.Should().BeApproximately(0.5f, 0.001f); + options.SystemPrompt.Should().Be("Custom prompt"); + } +} diff --git a/services/chat-service-net/tests/ChatService.UnitTests/Domain/Contracts/ChatHubClientTests.cs b/services/chat-service-net/tests/ChatService.UnitTests/Domain/Contracts/ChatHubClientTests.cs new file mode 100644 index 00000000..5d9c00b9 --- /dev/null +++ b/services/chat-service-net/tests/ChatService.UnitTests/Domain/Contracts/ChatHubClientTests.cs @@ -0,0 +1,66 @@ +using ChatService.Domain.Contracts; +using FluentAssertions; +using Xunit; + +namespace ChatService.UnitTests.Domain.Contracts; + +/// +/// EN: Unit tests for IChatHubClient interface and related DTOs. +/// VI: Unit tests cho IChatHubClient interface và các DTOs liên quan. +/// +public class ChatHubClientTests +{ + [Fact] + public void MessageNotification_ShouldCreateWithAllProperties() + { + // Arrange + var id = Guid.NewGuid(); + var conversationId = Guid.NewGuid(); + var senderId = "user-123"; + var senderName = "John Doe"; + var content = "Hello, World!"; + var messageType = "text"; + var sentAt = DateTime.UtcNow; + var replyToMessageId = Guid.NewGuid(); + + // Act + var notification = new MessageNotification( + Id: id, + ConversationId: conversationId, + SenderId: senderId, + SenderName: senderName, + Content: content, + MessageType: messageType, + SentAt: sentAt, + ReplyToMessageId: replyToMessageId + ); + + // Assert + notification.Id.Should().Be(id); + notification.ConversationId.Should().Be(conversationId); + notification.SenderId.Should().Be(senderId); + notification.SenderName.Should().Be(senderName); + notification.Content.Should().Be(content); + notification.MessageType.Should().Be(messageType); + notification.SentAt.Should().Be(sentAt); + notification.ReplyToMessageId.Should().Be(replyToMessageId); + } + + [Fact] + public void MessageNotification_ShouldAllowNullReplyToMessageId() + { + // Arrange & Act + var notification = new MessageNotification( + Id: Guid.NewGuid(), + ConversationId: Guid.NewGuid(), + SenderId: "user-123", + SenderName: "John Doe", + Content: "Hello!", + MessageType: "text", + SentAt: DateTime.UtcNow + ); + + // Assert + notification.ReplyToMessageId.Should().BeNull(); + } +} diff --git a/services/chat-service-net/tests/ChatService.UnitTests/Domain/Events/ConversationDomainEventsTests.cs b/services/chat-service-net/tests/ChatService.UnitTests/Domain/Events/ConversationDomainEventsTests.cs new file mode 100644 index 00000000..a519fa45 --- /dev/null +++ b/services/chat-service-net/tests/ChatService.UnitTests/Domain/Events/ConversationDomainEventsTests.cs @@ -0,0 +1,106 @@ +using ChatService.Domain.Events; +using FluentAssertions; +using Xunit; + +namespace ChatService.UnitTests.Domain.Events; + +/// +/// EN: Unit tests for Conversation Domain Events. +/// VI: Unit tests cho Conversation Domain Events. +/// +public class ConversationDomainEventsTests +{ + [Fact] + public void UserJoinedRoomDomainEvent_ShouldCreateWithCorrectProperties() + { + // Arrange + var userId = Guid.NewGuid(); + var conversationId = Guid.NewGuid(); + var userName = "John Doe"; + + // Act + var domainEvent = new UserJoinedRoomDomainEvent(userId, conversationId, userName); + + // Assert + domainEvent.UserId.Should().Be(userId); + domainEvent.ConversationId.Should().Be(conversationId); + domainEvent.UserName.Should().Be(userName); + domainEvent.JoinedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); + } + + [Fact] + public void UserLeftRoomDomainEvent_ShouldCreateWithCorrectProperties() + { + // Arrange + var userId = Guid.NewGuid(); + var conversationId = Guid.NewGuid(); + + // Act + var domainEvent = new UserLeftRoomDomainEvent(userId, conversationId); + + // Assert + domainEvent.UserId.Should().Be(userId); + domainEvent.ConversationId.Should().Be(conversationId); + domainEvent.LeftAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); + } + + [Fact] + public void TypingDomainEvent_ShouldCreateWithCorrectProperties_WhenTyping() + { + // Arrange + var userId = Guid.NewGuid(); + var conversationId = Guid.NewGuid(); + var userName = "Jane Smith"; + var isTyping = true; + + // Act + var domainEvent = new TypingDomainEvent(userId, conversationId, userName, isTyping); + + // Assert + domainEvent.UserId.Should().Be(userId); + domainEvent.ConversationId.Should().Be(conversationId); + domainEvent.UserName.Should().Be(userName); + domainEvent.IsTyping.Should().BeTrue(); + } + + [Fact] + public void TypingDomainEvent_ShouldCreateWithCorrectProperties_WhenStoppedTyping() + { + // Arrange + var userId = Guid.NewGuid(); + var conversationId = Guid.NewGuid(); + var userName = "Jane Smith"; + var isTyping = false; + + // Act + var domainEvent = new TypingDomainEvent(userId, conversationId, userName, isTyping); + + // Assert + domainEvent.IsTyping.Should().BeFalse(); + } + + [Fact] + public void MessageSentDomainEvent_ShouldImplementINotification() + { + // Assert + typeof(MessageSentDomainEvent).Should().Implement(); + } + + [Fact] + public void MessageReadDomainEvent_ShouldCreateWithCorrectProperties() + { + // Arrange + var messageId = Guid.NewGuid(); + var conversationId = Guid.NewGuid(); + var readerId = Guid.NewGuid(); + + // Act + var domainEvent = new MessageReadDomainEvent(messageId, conversationId, readerId); + + // Assert + domainEvent.MessageId.Should().Be(messageId); + domainEvent.ConversationId.Should().Be(conversationId); + domainEvent.ReaderId.Should().Be(readerId); + domainEvent.ReadAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); + } +} diff --git a/services/chat-service-net/tests/ChatService.UnitTests/Infrastructure/Services/AIServiceTests.cs b/services/chat-service-net/tests/ChatService.UnitTests/Infrastructure/Services/AIServiceTests.cs new file mode 100644 index 00000000..ea45a379 --- /dev/null +++ b/services/chat-service-net/tests/ChatService.UnitTests/Infrastructure/Services/AIServiceTests.cs @@ -0,0 +1,152 @@ +using ChatService.Domain.Contracts; +using ChatService.Infrastructure.Services; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace ChatService.UnitTests.Infrastructure.Services; + +/// +/// EN: Unit tests for NullAIService (used when OpenAI API key is not configured). +/// VI: Unit tests cho NullAIService (dùng khi chưa cấu hình OpenAI API key). +/// +public class NullAIServiceTests +{ + private readonly NullAIService _sut; + + public NullAIServiceTests() + { + _sut = new NullAIService(); + } + + [Fact] + public async Task IsAvailableAsync_ShouldReturnFalse() + { + // Act + var result = await _sut.IsAvailableAsync(); + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public async Task GetResponseAsync_ShouldReturnNotConfiguredMessage() + { + // Arrange + var prompt = "Hello AI"; + var history = Enumerable.Empty(); + + // Act + var result = await _sut.GetResponseAsync(prompt, history); + + // Assert + result.Should().Contain("not configured"); + } + + [Fact] + public async Task StreamResponseAsync_ShouldReturnNotConfiguredMessage() + { + // Arrange + var prompt = "Hello AI"; + var history = Enumerable.Empty(); + var chunks = new List(); + + // Act + await foreach (var chunk in _sut.StreamResponseAsync(prompt, history)) + { + chunks.Add(chunk); + } + + // Assert + chunks.Should().HaveCount(1); + chunks[0].Should().Contain("not configured"); + } + + [Fact] + public void NullAIService_ShouldImplementIAIService() + { + // Assert + _sut.Should().BeAssignableTo(); + } +} + +/// +/// EN: Unit tests for AIService (requires mocking HttpClient). +/// VI: Unit tests cho AIService (cần mock HttpClient). +/// +public class AIServiceTests +{ + [Fact] + public void AIService_ShouldImplementIAIService() + { + // Arrange + var httpClient = new HttpClient(); + var options = Options.Create(new AIServiceOptions()); + var logger = new Mock>().Object; + + // Act + var service = new AIService(httpClient, options, logger); + + // Assert + service.Should().BeAssignableTo(); + } + + [Fact] + public void AIService_ShouldUseProvidedOptions() + { + // Arrange + var httpClient = new HttpClient(); + var options = Options.Create(new AIServiceOptions + { + Model = "gpt-3.5-turbo", + MaxHistoryMessages = 10 + }); + var logger = new Mock>().Object; + + // Act + var service = new AIService(httpClient, options, logger); + + // Assert + service.Should().NotBeNull(); + } + + [Fact] + public async Task IsAvailableAsync_ShouldReturnFalse_WhenApiReturnsError() + { + // Arrange + var mockHandler = new MockHttpMessageHandler(new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized)); + var httpClient = new HttpClient(mockHandler); + var options = Options.Create(new AIServiceOptions()); + var logger = new Mock>().Object; + var service = new AIService(httpClient, options, logger); + + // Act + var result = await service.IsAvailableAsync(); + + // Assert + result.Should().BeFalse(); + } +} + +/// +/// EN: Mock HTTP message handler for testing. +/// VI: Mock HTTP message handler cho testing. +/// +internal class MockHttpMessageHandler : HttpMessageHandler +{ + private readonly HttpResponseMessage _response; + + public MockHttpMessageHandler(HttpResponseMessage response) + { + _response = response; + } + + protected override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + return Task.FromResult(_response); + } +} diff --git a/services/social-service-net/Dockerfile b/services/social-service-net/Dockerfile index fdbe7e10..277f9f2e 100644 --- a/services/social-service-net/Dockerfile +++ b/services/social-service-net/Dockerfile @@ -20,11 +20,11 @@ COPY src/ ./src/ # EN: Build the application # VI: Build ứng dụng WORKDIR "/src/src/SocialService.API" -RUN dotnet build "SocialService.API.csproj" -c Release -o /app/build --no-restore +RUN dotnet build "SocialService.API.csproj" -c Release -o /app/build # Publish stage / Giai đoạn publish FROM build AS publish -RUN dotnet publish "SocialService.API.csproj" -c Release -o /app/publish /p:UseAppHost=false --no-restore +RUN dotnet publish "SocialService.API.csproj" -c Release -o /app/publish /p:UseAppHost=false # Runtime stage / Giai đoạn runtime FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final diff --git a/services/social-service-net/docs/en/ARCHITECTURE.md b/services/social-service-net/docs/en/ARCHITECTURE.md index bc306599..3f187b44 100644 --- a/services/social-service-net/docs/en/ARCHITECTURE.md +++ b/services/social-service-net/docs/en/ARCHITECTURE.md @@ -1,45 +1,54 @@ -# Architecture Documentation +# Architecture Documentation - Social Service -> Detailed architecture documentation for the .NET 10 Microservice Template. +> Detailed architecture documentation for Social Service - Social relationship management service. ## Architecture Overview ```mermaid graph TB subgraph "API Layer" - C[Controllers] + RC[RelationshipsController] + BC[BlocksController] CMD[Commands] Q[Queries] B[Behaviors] - V[Validations] end subgraph "Domain Layer" - AR[Aggregate Roots] - E[Entities] - VO[Value Objects] + RA[Relationship Aggregate] + UBA[UserBlock Aggregate] + UPA[UserProfile Aggregate] DE[Domain Events] - DX[Domain Exceptions] end subgraph "Infrastructure Layer" DB[(PostgreSQL)] - R[Repositories] - CTX[DbContext] - ID[Idempotency] + RR[RelationshipRepository] + UBR[UserBlockRepository] + UPR[UserProfileRepository] + CTX[SocialServiceContext] end - C --> CMD - C --> Q - CMD --> B --> V - CMD --> AR - Q --> R - R --> CTX --> DB - AR --> DE - R --> AR + RC --> CMD + RC --> Q + BC --> CMD + BC --> Q + CMD --> B + CMD --> RA + CMD --> UBA + Q --> RR + Q --> UBR + RR --> CTX --> DB + UBR --> CTX + UPR --> CTX + RA --> DE + UBA --> DE - style C fill:#4a90d9,stroke:#2d5986,color:#fff - style AR fill:#50c878,stroke:#2d8659,color:#fff + style RC fill:#4a90d9,stroke:#2d5986,color:#fff + style BC fill:#4a90d9,stroke:#2d5986,color:#fff + style RA fill:#50c878,stroke:#2d8659,color:#fff + style UBA fill:#50c878,stroke:#2d8659,color:#fff + style UPA fill:#50c878,stroke:#2d8659,color:#fff style DB fill:#ff6b6b,stroke:#c0392b,color:#fff ``` @@ -47,34 +56,51 @@ graph TB ### 1. Domain Layer (SocialService.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 +The heart of the application containing pure business logic. -#### Components +#### Aggregates -| Component | Purpose | +| Aggregate | 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 | +| **Relationship** | Manages friendship and following | +| **UserBlock** | Manages user blocking | +| **UserProfile** | Caches user info from IAM Service | + +#### Domain Events + +| Event | Trigger | +|-------|---------| +| `FriendRequestSentDomainEvent` | Friend request sent | +| `FriendshipCreatedDomainEvent` | Friend request accepted | +| `UserFollowedDomainEvent` | User followed | +| `RelationshipStatusChangedDomainEvent` | Relationship status changed | +| `RelationshipRemovedDomainEvent` | Unfriend/unfollow | +| `UserBlockedDomainEvent` | User blocked | +| `UserUnblockedDomainEvent` | User unblocked | ### 2. Infrastructure Layer (SocialService.Infrastructure) -Technical implementations and external concerns: -- Database access (EF Core) -- Repository implementations -- External service integrations +Technical implementation and data access. + +| Component | Purpose | +|-----------|---------| +| **SocialServiceContext** | DbContext with Unit of Work pattern | +| **RelationshipRepository** | CRUD for Relationship aggregate | +| **UserBlockRepository** | CRUD for UserBlock aggregate | +| **UserProfileRepository** | CRUD for UserProfile aggregate | +| **EntityConfigurations** | EF Core Fluent API mappings | ### 3. API Layer (SocialService.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 +Application entry point and CQRS implementation. + +| Component | Purpose | +|-----------|---------| +| **RelationshipsController** | APIs for friendship and following | +| **BlocksController** | APIs for block/unblock users | +| **Commands** | 6 write operations (MediatR) | +| **Queries** | 4 read operations | +| **Behaviors** | Logging, Validation, Transaction | ## CQRS Flow @@ -86,7 +112,7 @@ sequenceDiagram participant LoggingBehavior participant ValidatorBehavior participant TransactionBehavior - participant CommandHandler + participant Handler participant Repository participant DbContext @@ -95,94 +121,118 @@ sequenceDiagram MediatR->>LoggingBehavior: Handle LoggingBehavior->>ValidatorBehavior: Next() ValidatorBehavior->>TransactionBehavior: Next() - TransactionBehavior->>CommandHandler: Next() - CommandHandler->>Repository: Add/Update/Delete + TransactionBehavior->>Handler: Next() + Handler->>Repository: Add/Update/Delete Repository->>DbContext: SaveEntitiesAsync() - DbContext-->>Repository: Success - Repository-->>CommandHandler: Result - CommandHandler-->>Controller: Response + DbContext-->>Repository: Success + Dispatch Events + Repository-->>Handler: Result + Handler-->>Controller: Response Controller-->>Client: HTTP Response ``` -## Domain Events +## Relationship State Machine ```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] +stateDiagram-v2 + [*] --> Pending: SendFriendRequest + Pending --> Accepted: Accept() + Pending --> Rejected: Reject() + Pending --> Cancelled: Cancel() + Accepted --> Cancelled: Remove() - style AR fill:#50c878,stroke:#2d8659,color:#fff - style DE fill:#f39c12,stroke:#d68910,color:#fff - style M fill:#9b59b6,stroke:#7d3c98,color:#fff + [*] --> Accepted: FollowUser (auto-accept) + + note right of Pending: For Friendship type only + note right of Accepted: Following is auto-Accepted ``` ## Database Schema -### Sample Aggregate - ```mermaid erDiagram - samples { + relationships { uuid id PK - varchar(200) name - varchar(1000) description + uuid requester_id FK + uuid addressee_id FK + int type_id FK int status_id FK timestamp created_at timestamp updated_at } - sample_statuses { + relationship_types { int id PK varchar(50) name } - samples ||--o{ sample_statuses : has + relationship_statuses { + int id PK + varchar(50) name + } + + user_blocks { + uuid id PK + uuid blocker_id FK + uuid blocked_id FK + varchar(500) reason + timestamp created_at + } + + user_profiles { + uuid id PK + uuid user_id UK + varchar(100) display_name + varchar(500) avatar_url + varchar(500) bio + timestamp last_synced_at + timestamp created_at + } + + relationships ||--o| relationship_types : has_type + relationships ||--o| relationship_statuses : has_status ``` -## MediatR Pipeline +### Enumeration Values -``` -Request → LoggingBehavior → ValidatorBehavior → TransactionBehavior → Handler → Response - │ │ │ - ▼ ▼ ▼ - Log start/end Validate Begin/Commit - + timing with Transaction - FluentValidation -``` +**RelationshipType:** +| ID | Name | +|----|------| +| 1 | Friendship | +| 2 | Following | -### Behavior Order +**RelationshipStatus:** +| ID | Name | +|----|------| +| 1 | Pending | +| 2 | Accepted | +| 3 | Rejected | +| 4 | Cancelled | -1. **LoggingBehavior** - Logs request handling with timing -2. **ValidatorBehavior** - Validates request using FluentValidation -3. **TransactionBehavior** - Wraps command handlers in database transactions +## Domain Events Flow -## 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"] - } -} +```mermaid +graph LR + subgraph "Relationship Aggregate" + R[Relationship] -->|Creates| E1[FriendRequestSentEvent] + R -->|Accept| E2[FriendshipCreatedEvent] + R -->|Follow| E3[UserFollowedEvent] + R -->|Any Change| E4[StatusChangedEvent] + R -->|Remove| E5[RemovedEvent] + end + + subgraph "UserBlock Aggregate" + UB[UserBlock] -->|Creates| E6[UserBlockedEvent] + end + + subgraph "Event Handlers" + E1 --> H1[Notification Handler] + E2 --> H2[Notification Handler] + E3 --> H3[Feed Handler] + E6 --> H4[Cleanup Handler] + end + + style R fill:#50c878,stroke:#2d8659,color:#fff + style UB fill:#50c878,stroke:#2d8659,color:#fff ``` ## Health Checks @@ -250,18 +300,38 @@ spec: ## Security Considerations -1. **Authentication**: JWT Bearer token (configure in production) -2. **Authorization**: Role-based access control +1. **Authentication**: JWT Bearer token from IAM Service +2. **Authorization**: Ownership check in business logic 3. **Input Validation**: FluentValidation on all requests -4. **SQL Injection**: EF Core parameterized queries -5. **Secrets**: Environment variables, never in code +4. **Block Enforcement**: Blocking a user prevents all interactions +5. **SQL Injection**: EF Core parameterized queries + +## Integration with Other Services + +```mermaid +graph LR + SS[Social Service] <-->|Integration Events| EB[Event Bus] + EB <--> IAM[IAM Service] + EB <--> NS[Notification Service] + EB <--> FS[Feed Service] + + IAM -->|UserCreated/Updated| SS + SS -->|FriendshipCreated| NS + SS -->|UserFollowed| FS + + style SS fill:#4a90d9,stroke:#2d5986,color:#fff + style EB fill:#9b59b6,stroke:#7d3c98,color:#fff +``` ## 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 +3. **Pagination**: All list queries support skip/take +4. **Indexes**: + - `relationships`: (requester_id, addressee_id, type_id) + - `user_blocks`: (blocker_id, blocked_id) + - `user_profiles`: (user_id) UNIQUE ## References diff --git a/services/social-service-net/docs/en/README.md b/services/social-service-net/docs/en/README.md index d1baaa64..b1d512b5 100644 --- a/services/social-service-net/docs/en/README.md +++ b/services/social-service-net/docs/en/README.md @@ -1,19 +1,25 @@ -# .NET 10 Microservice Template +# Social Service - Social Relationship Management -> Enterprise-grade .NET 10 microservice template following DDD, CQRS, and Clean Architecture patterns. +> .NET 10 microservice for managing social relationships between users: friendship, following, and blocking. ## Overview -This template provides a production-ready structure for .NET microservices based on the eShopOnContainers reference architecture with: +Social Service manages all social relationships between users in the GoodGo Platform: -- **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 +- **Friendship** - Send/accept/reject friend requests, unfriend +- **Following** - Follow/unfollow users +- **Blocking** - Block/unblock users with optional reason +- **User Profile** - Sync user info from IAM Service + +### Architecture & Patterns + +| Pattern | Implementation | +|---------|---------------| +| **DDD** | Aggregates, Entities, Value Objects, Domain Events | +| **CQRS** | Commands/Queries separated with MediatR | +| **Clean Architecture** | 3-layer: Domain, Infrastructure, API | +| **EF Core 10** | PostgreSQL with connection resilience | +| **Structured Logging** | Serilog with console and Seq | ## Prerequisites @@ -21,41 +27,11 @@ This template provides a production-ready structure for .NET microservices based |-------------|---------| | .NET SDK | 10.0.101+ | | Docker | 24.0+ | -| PostgreSQL | 15+ (or use Docker) | - -```bash -# Check .NET version -dotnet --version -# Should output: 10.0.xxx -``` +| PostgreSQL | 15+ | ## 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 "SocialService" to "YourService" -find . -type f -name "*.cs" -exec sed -i '' 's/SocialService/YourService/g' {} + -find . -type f -name "*.csproj" -exec sed -i '' 's/SocialService/YourService/g' {} + -``` - -### 2. Configure Environment - -```bash -# Copy environment template -cp .env.example .env - -# Edit with your configuration -nano .env -``` - -### 3. Run with Docker +### Run with Docker ```bash # Start all services (API + PostgreSQL + Redis) @@ -65,7 +41,7 @@ docker-compose up -d docker-compose logs -f socialservice-api ``` -### 4. Run Locally +### Run Locally ```bash # Restore dependencies @@ -78,53 +54,101 @@ dotnet build dotnet run --project src/SocialService.API ``` -## Project Structure +## Domain Model +### Aggregates + +#### 1. Relationship Aggregate +Manages friendship and following relationships between users. + +```mermaid +classDiagram + class Relationship { + +Guid Id + +Guid RequesterId + +Guid AddresseeId + +RelationshipType Type + +RelationshipStatus Status + +DateTime CreatedAt + +DateTime? UpdatedAt + +Accept() + +Reject() + +Cancel() + +Remove() + } + + class RelationshipType { + <> + Friendship + Following + } + + class RelationshipStatus { + <> + Pending + Accepted + Rejected + Cancelled + } + + Relationship --> RelationshipType + Relationship --> RelationshipStatus ``` -_template_dot_net/ -├── src/ -│ ├── SocialService.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 -│ │ -│ ├── SocialService.Domain/ # Domain Layer (Pure business logic) -│ │ ├── AggregatesModel/ # Aggregate roots and entities -│ │ ├── Events/ # Domain events -│ │ ├── Exceptions/ # Domain exceptions -│ │ └── SeedWork/ # Base classes (Entity, ValueObject, etc.) -│ │ -│ └── SocialService.Infrastructure/ # Infrastructure Layer (Data access) -│ ├── EntityConfigurations/ # EF Core Fluent API configurations -│ ├── Repositories/ # Repository implementations -│ ├── Idempotency/ # Request idempotency handling -│ └── SocialServiceContext.cs # DbContext with Unit of Work -│ -├── tests/ -│ ├── SocialService.UnitTests/ # Unit tests (Domain, Application) -│ └── SocialService.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 -``` + +**Business Rules:** +- Following is auto-accepted +- Friendship requires confirmation from recipient +- Cannot create relationship with yourself +- Only Pending requests can be Accept/Reject/Cancel +- Only Accepted relationships can be Removed + +#### 2. UserBlock Aggregate +Manages user blocking. + +| Field | Description | +|-------|-------------| +| `BlockerId` | ID of blocking user | +| `BlockedId` | ID of blocked user | +| `Reason` | Block reason (optional) | +| `CreatedAt` | Block timestamp | + +#### 3. UserProfile Aggregate +Caches user info synced from IAM Service. + +| Field | Description | +|-------|-------------| +| `UserId` | User ID from IAM | +| `DisplayName` | Display name | +| `AvatarUrl` | Avatar URL | +| `Bio` | User bio | +| `LastSyncedAt` | Last sync time | ## API Endpoints +### Relationships - Friendship + | 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 | +| `GET` | `/api/v1/relationships/users/{userId}/friends` | Get friend list | +| `POST` | `/api/v1/relationships/friend-requests` | Send friend request | +| `PUT` | `/api/v1/relationships/friend-requests/{id}` | Respond (accept/reject) | +| `GET` | `/api/v1/relationships/users/{id1}/mutual-friends/{id2}` | Get mutual friends | +| `GET` | `/api/v1/relationships/users/{userId}/suggestions` | Friend suggestions | + +### Relationships - Following + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/api/v1/relationships/follow` | Follow user | +| `DELETE` | `/api/v1/relationships/follow` | Unfollow user | + +### Block Users + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/api/v1/blocks` | Block user | +| `DELETE` | `/api/v1/blocks` | Unblock user | +| `GET` | `/api/v1/blocks/users/{userId}` | Get blocked users list | ### Health Endpoints @@ -134,74 +158,79 @@ _template_dot_net/ | `/health/live` | Liveness probe | | `/health/ready` | Readiness probe | +## Project Structure + +``` +social-service-net/ +├── src/ +│ ├── SocialService.API/ +│ │ ├── Controllers/ +│ │ │ ├── RelationshipsController.cs # Friends + Following APIs +│ │ │ └── BlocksController.cs # Block/Unblock APIs +│ │ ├── Application/ +│ │ │ ├── Commands/ # 6 commands +│ │ │ ├── Queries/ # 4 queries +│ │ │ ├── Behaviors/ # MediatR pipeline +│ │ │ └── Validations/ # FluentValidation +│ │ └── Program.cs +│ │ +│ ├── SocialService.Domain/ +│ │ ├── AggregatesModel/ +│ │ │ ├── RelationshipAggregate/ # Relationship, Status, Type +│ │ │ ├── UserBlockAggregate/ # UserBlock +│ │ │ └── UserProfileAggregate/ # UserProfile (IAM sync) +│ │ ├── Events/ # 7 domain events +│ │ ├── Exceptions/ # SocialDomainException +│ │ └── SeedWork/ # Base classes +│ │ +│ └── SocialService.Infrastructure/ +│ ├── EntityConfigurations/ # EF Core mappings +│ ├── Repositories/ # 3 repositories +│ ├── Idempotency/ # Request deduplication +│ └── SocialServiceContext.cs # DbContext +│ +├── tests/ +│ ├── SocialService.UnitTests/ +│ └── SocialService.FunctionalTests/ +│ +├── Dockerfile +├── docker-compose.yml +└── docs/ +``` + ## 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); - } -} -``` +| Command | Description | +|---------|-------------| +| `SendFriendRequestCommand` | Send friend request | +| `RespondToFriendRequestCommand` | Accept/Reject friend request | +| `FollowUserCommand` | Follow user | +| `UnfollowUserCommand` | Unfollow user | +| `BlockUserCommand` | Block user with optional reason | +| `UnblockUserCommand` | Unblock user | ### Queries (Read Operations) -```csharp -// Define query -public record GetSampleQuery(Guid SampleId) : IRequest; -``` +| Query | Description | +|-------|-------------| +| `GetFriendsQuery` | Friend list with pagination | +| `GetMutualFriendsQuery` | Mutual friends between 2 users | +| `GetFriendSuggestionsQuery` | Friend suggestions | +| `GetBlockedUsersQuery` | Blocked users list | -## Domain Model +## Domain Events -### 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/SocialService.UnitTests -``` +| Event | Trigger | +|-------|---------| +| `FriendRequestSentDomainEvent` | Friend request sent | +| `FriendshipCreatedDomainEvent` | Friend request accepted | +| `UserFollowedDomainEvent` | User followed | +| `RelationshipStatusChangedDomainEvent` | Status changed | +| `RelationshipRemovedDomainEvent` | Unfriend/unfollow | +| `UserBlockedDomainEvent` | User blocked | +| `UserUnblockedDomainEvent` | User unblocked | ## Configuration @@ -212,7 +241,6 @@ dotnet test tests/SocialService.UnitTests | `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 @@ -227,6 +255,19 @@ dotnet test tests/SocialService.UnitTests } ``` +## Testing + +```bash +# Run all tests +dotnet test + +# Run with coverage +dotnet test /p:CollectCoverage=true + +# Run specific test project +dotnet test tests/SocialService.UnitTests +``` + ## Deployment ### Docker Build @@ -239,26 +280,11 @@ docker build -t socialservice:latest . docker run -p 5000:8080 --env-file .env socialservice: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 +- [Architecture Documentation](./ARCHITECTURE.md) - [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 diff --git a/services/social-service-net/docs/vi/ARCHITECTURE.md b/services/social-service-net/docs/vi/ARCHITECTURE.md index 98d304d7..d0602c28 100644 --- a/services/social-service-net/docs/vi/ARCHITECTURE.md +++ b/services/social-service-net/docs/vi/ARCHITECTURE.md @@ -1,45 +1,54 @@ -# Tài Liệu Kiến Trúc +# Tài Liệu Kiến Trúc - Social Service -> Tài liệu kiến trúc chi tiết cho Template Microservice .NET 10. +> Tài liệu kiến trúc chi tiết cho Social Service - Dịch vụ quản lý quan hệ xã hội. ## Tổng Quan Kiến Trúc ```mermaid graph TB subgraph "Lớp API" - C[Controllers] + RC[RelationshipsController] + BC[BlocksController] CMD[Commands] Q[Queries] B[Behaviors] - V[Validations] end subgraph "Lớp Domain" - AR[Aggregate Roots] - E[Entities] - VO[Value Objects] + RA[Relationship Aggregate] + UBA[UserBlock Aggregate] + UPA[UserProfile Aggregate] DE[Domain Events] - DX[Domain Exceptions] end subgraph "Lớp Infrastructure" DB[(PostgreSQL)] - R[Repositories] - CTX[DbContext] - ID[Idempotency] + RR[RelationshipRepository] + UBR[UserBlockRepository] + UPR[UserProfileRepository] + CTX[SocialServiceContext] end - C --> CMD - C --> Q - CMD --> B --> V - CMD --> AR - Q --> R - R --> CTX --> DB - AR --> DE - R --> AR + RC --> CMD + RC --> Q + BC --> CMD + BC --> Q + CMD --> B + CMD --> RA + CMD --> UBA + Q --> RR + Q --> UBR + RR --> CTX --> DB + UBR --> CTX + UPR --> CTX + RA --> DE + UBA --> DE - style C fill:#4a90d9,stroke:#2d5986,color:#fff - style AR fill:#50c878,stroke:#2d8659,color:#fff + style RC fill:#4a90d9,stroke:#2d5986,color:#fff + style BC fill:#4a90d9,stroke:#2d5986,color:#fff + style RA fill:#50c878,stroke:#2d8659,color:#fff + style UBA fill:#50c878,stroke:#2d8659,color:#fff + style UPA fill:#50c878,stroke:#2d8659,color:#fff style DB fill:#ff6b6b,stroke:#c0392b,color:#fff ``` @@ -47,34 +56,51 @@ graph TB ### 1. Lớp Domain (SocialService.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 +Trái tim của ứng dụng chứa business logic thuần túy. -#### Thành Phần +#### Aggregates -| 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 | +| Aggregate | Mục Đích | +|-----------|----------| +| **Relationship** | Quản lý kết bạn (Friendship) và theo dõi (Following) | +| **UserBlock** | Quản lý việc block users | +| **UserProfile** | Cache thông tin user từ IAM Service | + +#### Domain Events + +| Event | Trigger | +|-------|---------| +| `FriendRequestSentDomainEvent` | Gửi yêu cầu kết bạn | +| `FriendshipCreatedDomainEvent` | Chấp nhận kết bạn | +| `UserFollowedDomainEvent` | Follow user | +| `RelationshipStatusChangedDomainEvent` | Thay đổi trạng thái quan hệ | +| `RelationshipRemovedDomainEvent` | Hủy kết bạn/bỏ theo dõi | +| `UserBlockedDomainEvent` | Block user | +| `UserUnblockedDomainEvent` | Unblock user | ### 2. Lớp Infrastructure (SocialService.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 +Triển khai kỹ thuật và truy cập dữ liệu. + +| Component | Mục Đích | +|-----------|----------| +| **SocialServiceContext** | DbContext với Unit of Work pattern | +| **RelationshipRepository** | CRUD cho Relationship aggregate | +| **UserBlockRepository** | CRUD cho UserBlock aggregate | +| **UserProfileRepository** | CRUD cho UserProfile aggregate | +| **EntityConfigurations** | EF Core Fluent API mappings | ### 3. Lớp API (SocialService.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 +Điểm vào ứng dụng và triển khai CQRS. + +| Component | Mục Đích | +|-----------|----------| +| **RelationshipsController** | APIs cho kết bạn và theo dõi | +| **BlocksController** | APIs cho block/unblock users | +| **Commands** | 6 write operations (MediatR) | +| **Queries** | 4 read operations | +| **Behaviors** | Logging, Validation, Transaction | ## Luồng CQRS @@ -86,7 +112,7 @@ sequenceDiagram participant LoggingBehavior participant ValidatorBehavior participant TransactionBehavior - participant CommandHandler + participant Handler participant Repository participant DbContext @@ -95,94 +121,118 @@ sequenceDiagram MediatR->>LoggingBehavior: Handle LoggingBehavior->>ValidatorBehavior: Next() ValidatorBehavior->>TransactionBehavior: Next() - TransactionBehavior->>CommandHandler: Next() - CommandHandler->>Repository: Add/Update/Delete + TransactionBehavior->>Handler: Next() + Handler->>Repository: Add/Update/Delete Repository->>DbContext: SaveEntitiesAsync() - DbContext-->>Repository: Success - Repository-->>CommandHandler: Result - CommandHandler-->>Controller: Response + DbContext-->>Repository: Success + Dispatch Events + Repository-->>Handler: Result + Handler-->>Controller: Response Controller-->>Client: HTTP Response ``` -## Domain Events +## Relationship State Machine ```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] +stateDiagram-v2 + [*] --> Pending: SendFriendRequest + Pending --> Accepted: Accept() + Pending --> Rejected: Reject() + Pending --> Cancelled: Cancel() + Accepted --> Cancelled: Remove() - style AR fill:#50c878,stroke:#2d8659,color:#fff - style DE fill:#f39c12,stroke:#d68910,color:#fff - style M fill:#9b59b6,stroke:#7d3c98,color:#fff + [*] --> Accepted: FollowUser (auto-accept) + + note right of Pending: Chỉ cho Friendship type + note right of Accepted: Follow tự động Accepted ``` ## Schema Database -### Sample Aggregate - ```mermaid erDiagram - samples { + relationships { uuid id PK - varchar(200) name - varchar(1000) description + uuid requester_id FK + uuid addressee_id FK + int type_id FK int status_id FK timestamp created_at timestamp updated_at } - sample_statuses { + relationship_types { int id PK varchar(50) name } - samples ||--o{ sample_statuses : has + relationship_statuses { + int id PK + varchar(50) name + } + + user_blocks { + uuid id PK + uuid blocker_id FK + uuid blocked_id FK + varchar(500) reason + timestamp created_at + } + + user_profiles { + uuid id PK + uuid user_id UK + varchar(100) display_name + varchar(500) avatar_url + varchar(500) bio + timestamp last_synced_at + timestamp created_at + } + + relationships ||--o| relationship_types : has_type + relationships ||--o| relationship_statuses : has_status ``` -## Pipeline MediatR +### Enumeration Values -``` -Request → LoggingBehavior → ValidatorBehavior → TransactionBehavior → Handler → Response - │ │ │ - ▼ ▼ ▼ - Log start/end Validate Begin/Commit - + timing với Transaction - FluentValidation -``` +**RelationshipType:** +| ID | Name | +|----|------| +| 1 | Friendship | +| 2 | Following | -### Thứ Tự Behaviors +**RelationshipStatus:** +| ID | Name | +|----|------| +| 1 | Pending | +| 2 | Accepted | +| 3 | Rejected | +| 4 | Cancelled | -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 +## Domain Events Flow -## 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"] - } -} +```mermaid +graph LR + subgraph "Relationship Aggregate" + R[Relationship] -->|Creates| E1[FriendRequestSentEvent] + R -->|Accept| E2[FriendshipCreatedEvent] + R -->|Follow| E3[UserFollowedEvent] + R -->|Any Change| E4[StatusChangedEvent] + R -->|Remove| E5[RemovedEvent] + end + + subgraph "UserBlock Aggregate" + UB[UserBlock] -->|Creates| E6[UserBlockedEvent] + end + + subgraph "Event Handlers" + E1 --> H1[Notification Handler] + E2 --> H2[Notification Handler] + E3 --> H3[Feed Handler] + E6 --> H4[Cleanup Handler] + end + + style R fill:#50c878,stroke:#2d8659,color:#fff + style UB fill:#50c878,stroke:#2d8659,color:#fff ``` ## Health Checks @@ -250,18 +300,38 @@ spec: ## 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 +1. **Authentication**: JWT Bearer token từ IAM Service +2. **Authorization**: Kiểm tra ownership trong business logic 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 +4. **Block Enforcement**: Block một user sẽ ngăn mọi tương tác +5. **SQL Injection**: EF Core parameterized queries + +## Tích Hợp Với Các Services Khác + +```mermaid +graph LR + SS[Social Service] <-->|Integration Events| EB[Event Bus] + EB <--> IAM[IAM Service] + EB <--> NS[Notification Service] + EB <--> FS[Feed Service] + + IAM -->|UserCreated/Updated| SS + SS -->|FriendshipCreated| NS + SS -->|UserFollowed| FS + + style SS fill:#4a90d9,stroke:#2d5986,color:#fff + style EB fill:#9b59b6,stroke:#7d3c98,color:#fff +``` ## 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 +3. **Pagination**: Tất cả list queries hỗ trợ skip/take +4. **Indexes**: + - `relationships`: (requester_id, addressee_id, type_id) + - `user_blocks`: (blocker_id, blocked_id) + - `user_profiles`: (user_id) UNIQUE ## Tài Liệu Tham Khảo diff --git a/services/social-service-net/docs/vi/README.md b/services/social-service-net/docs/vi/README.md index e188f513..c428c92d 100644 --- a/services/social-service-net/docs/vi/README.md +++ b/services/social-service-net/docs/vi/README.md @@ -1,19 +1,25 @@ -# Template Microservice .NET 10 +# Social Service - Dịch Vụ Quản Lý Quan Hệ Xã Hội -> Template microservice .NET 10 cấp doanh nghiệp theo các pattern DDD, CQRS và Clean Architecture. +> Microservice .NET 10 quản lý quan hệ xã hội giữa users: kết bạn, theo dõi và block. ## 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: +Social Service là microservice quản lý tất cả các mối quan hệ xã hội giữa users trong GoodGo Platform: -- **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 +- **Kết bạn (Friendship)** - Gửi/chấp nhận/từ chối yêu cầu kết bạn, hủy kết bạn +- **Theo dõi (Following)** - Follow/unfollow users +- **Block (User Blocking)** - Block/unblock users với lý do tùy chọn +- **User Profile** - Đồng bộ thông tin user từ IAM Service + +### Kiến Trúc & Patterns + +| Pattern | Triển khai | +|---------|-----------| +| **DDD** | Aggregates, Entities, Value Objects, Domain Events | +| **CQRS** | Commands/Queries tách biệt với MediatR | +| **Clean Architecture** | 3-layer: Domain, Infrastructure, API | +| **EF Core 10** | PostgreSQL với connection resilience | +| **Structured Logging** | Serilog với console và Seq | ## Yêu Cầu @@ -21,41 +27,11 @@ Template này cung cấp cấu trúc sẵn sàng production cho microservices .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 -``` +| PostgreSQL | 15+ | ## 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ả "SocialService" thành "YourService" -find . -type f -name "*.cs" -exec sed -i '' 's/SocialService/YourService/g' {} + -find . -type f -name "*.csproj" -exec sed -i '' 's/SocialService/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 +### Chạy với Docker ```bash # Khởi động tất cả services (API + PostgreSQL + Redis) @@ -65,7 +41,7 @@ docker-compose up -d docker-compose logs -f socialservice-api ``` -### 4. Chạy Local +### Chạy Local ```bash # Khôi phục dependencies @@ -78,53 +54,101 @@ dotnet build dotnet run --project src/SocialService.API ``` -## Cấu Trúc Dự Án +## Domain Model -``` -_template_dot_net/ -├── src/ -│ ├── SocialService.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 -│ │ -│ ├── SocialService.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.) -│ │ -│ └── SocialService.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 -│ └── SocialServiceContext.cs # DbContext với Unit of Work -│ -├── tests/ -│ ├── SocialService.UnitTests/ # Unit tests (Domain, Application) -│ └── SocialService.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 +### Aggregates + +#### 1. Relationship Aggregate +Quản lý quan hệ kết bạn và theo dõi giữa users. + +```mermaid +classDiagram + class Relationship { + +Guid Id + +Guid RequesterId + +Guid AddresseeId + +RelationshipType Type + +RelationshipStatus Status + +DateTime CreatedAt + +DateTime? UpdatedAt + +Accept() + +Reject() + +Cancel() + +Remove() + } + + class RelationshipType { + <> + Friendship + Following + } + + class RelationshipStatus { + <> + Pending + Accepted + Rejected + Cancelled + } + + Relationship --> RelationshipType + Relationship --> RelationshipStatus ``` -## Các Endpoint API +**Business Rules:** +- Following tự động được chấp nhận +- Friendship yêu cầu xác nhận từ người nhận +- Không thể tạo quan hệ với chính mình +- Chỉ Pending requests mới có thể Accept/Reject/Cancel +- Chỉ Accepted relationships mới có thể Remove + +#### 2. UserBlock Aggregate +Quản lý việc block users. + +| Field | Mô tả | +|-------|-------| +| `BlockerId` | ID user thực hiện block | +| `BlockedId` | ID user bị block | +| `Reason` | Lý do block (tùy chọn) | +| `CreatedAt` | Thời gian block | + +#### 3. UserProfile Aggregate +Cache thông tin user được đồng bộ từ IAM Service. + +| Field | Mô tả | +|-------|-------| +| `UserId` | ID user từ IAM | +| `DisplayName` | Tên hiển thị | +| `AvatarUrl` | URL avatar | +| `Bio` | Tiểu sử | +| `LastSyncedAt` | Thời gian sync cuối | + +## API Endpoints + +### Relationships - Kết Bạn | 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 | +| `GET` | `/api/v1/relationships/users/{userId}/friends` | Lấy danh sách bạn bè | +| `POST` | `/api/v1/relationships/friend-requests` | Gửi yêu cầu kết bạn | +| `PUT` | `/api/v1/relationships/friend-requests/{id}` | Phản hồi yêu cầu (accept/reject) | +| `GET` | `/api/v1/relationships/users/{id1}/mutual-friends/{id2}` | Lấy bạn chung | +| `GET` | `/api/v1/relationships/users/{userId}/suggestions` | Gợi ý kết bạn | + +### Relationships - Theo Dõi + +| Method | Endpoint | Mô Tả | +|--------|----------|-------| +| `POST` | `/api/v1/relationships/follow` | Theo dõi user | +| `DELETE` | `/api/v1/relationships/follow` | Bỏ theo dõi user | + +### Block Users + +| Method | Endpoint | Mô Tả | +|--------|----------|-------| +| `POST` | `/api/v1/blocks` | Block user | +| `DELETE` | `/api/v1/blocks` | Unblock user | +| `GET` | `/api/v1/blocks/users/{userId}` | Lấy danh sách blocked users | ### Health Endpoints @@ -134,74 +158,79 @@ _template_dot_net/ | `/health/live` | Kiểm tra sống | | `/health/ready` | Kiểm tra sẵn sàng | -## Pattern CQRS +## Cấu Trúc Dự Án + +``` +social-service-net/ +├── src/ +│ ├── SocialService.API/ +│ │ ├── Controllers/ +│ │ │ ├── RelationshipsController.cs # Friends + Following APIs +│ │ │ └── BlocksController.cs # Block/Unblock APIs +│ │ ├── Application/ +│ │ │ ├── Commands/ # 6 commands +│ │ │ ├── Queries/ # 4 queries +│ │ │ ├── Behaviors/ # MediatR pipeline +│ │ │ └── Validations/ # FluentValidation +│ │ └── Program.cs +│ │ +│ ├── SocialService.Domain/ +│ │ ├── AggregatesModel/ +│ │ │ ├── RelationshipAggregate/ # Relationship, Status, Type +│ │ │ ├── UserBlockAggregate/ # UserBlock +│ │ │ └── UserProfileAggregate/ # UserProfile (IAM sync) +│ │ ├── Events/ # 7 domain events +│ │ ├── Exceptions/ # SocialDomainException +│ │ └── SeedWork/ # Base classes +│ │ +│ └── SocialService.Infrastructure/ +│ ├── EntityConfigurations/ # EF Core mappings +│ ├── Repositories/ # 3 repositories +│ ├── Idempotency/ # Request deduplication +│ └── SocialServiceContext.cs # DbContext +│ +├── tests/ +│ ├── SocialService.UnitTests/ +│ └── SocialService.FunctionalTests/ +│ +├── Dockerfile +├── docker-compose.yml +└── docs/ +``` + +## CQRS Pattern ### 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); - } -} -``` +| Command | Mô Tả | +|---------|-------| +| `SendFriendRequestCommand` | Gửi yêu cầu kết bạn | +| `RespondToFriendRequestCommand` | Accept/Reject friend request | +| `FollowUserCommand` | Theo dõi user | +| `UnfollowUserCommand` | Bỏ theo dõi | +| `BlockUserCommand` | Block user với lý do tùy chọn | +| `UnblockUserCommand` | Unblock user | ### Queries (Thao Tác Đọc) -```csharp -// Định nghĩa query -public record GetSampleQuery(Guid SampleId) : IRequest; -``` +| Query | Mô Tả | +|-------|-------| +| `GetFriendsQuery` | Danh sách bạn bè với phân trang | +| `GetMutualFriendsQuery` | Bạn chung giữa 2 users | +| `GetFriendSuggestionsQuery` | Gợi ý kết bạn | +| `GetBlockedUsersQuery` | Danh sách users bị block | -## Domain Model +## Domain Events -### 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/SocialService.UnitTests -``` +| Event | Trigger | +|-------|---------| +| `FriendRequestSentDomainEvent` | Khi gửi friend request | +| `FriendshipCreatedDomainEvent` | Khi friend request được accept | +| `UserFollowedDomainEvent` | Khi follow user | +| `RelationshipStatusChangedDomainEvent` | Khi status thay đổi | +| `RelationshipRemovedDomainEvent` | Khi unfriend/unfollow | +| `UserBlockedDomainEvent` | Khi block user | +| `UserUnblockedDomainEvent` | Khi unblock user | ## Cấu Hình @@ -210,9 +239,8 @@ dotnet test tests/SocialService.UnitTests | 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ự) | - | +| `DATABASE_URL` | PostgreSQL connection string | - | +| `REDIS_URL` | Redis connection string | - | ### appsettings.json @@ -227,6 +255,19 @@ dotnet test tests/SocialService.UnitTests } ``` +## Kiểm Thử + +```bash +# Chạy tất cả tests +dotnet test + +# Chạy với coverage +dotnet test /p:CollectCoverage=true + +# Chạy project test cụ thể +dotnet test tests/SocialService.UnitTests +``` + ## Triển Khai ### Docker Build @@ -239,26 +280,11 @@ docker build -t socialservice:latest . docker run -p 5000:8080 --env-file .env socialservice: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 +- [Tài liệu Kiến trúc](./ARCHITECTURE.md) - [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