# Domain Models - MKT Zalo Service

Domain-Driven Design implementation for Zalo marketing service.

## Aggregates

### 1. Conversation Aggregate

Manages conversation lifecycle and messages.

```csharp
public class Conversation : Entity, IAggregateRoot
{
    private readonly List<Message> _messages = new();
    
    public string ZaloUserId { get; private set; }
    public Guid CustomerId { get; private set; }
    public ConversationStatus Status { get; private set; }
    public DateTime StartedAt { get; private set; }
    public DateTime? EndedAt { get; private set; }
    public IReadOnlyCollection<Message> Messages => _messages.AsReadOnly();
    
    public void AddMessage(MessageType type, string content, 
        MessageDirection direction, bool isFromBot)
    {
        if (Status == ConversationStatus.Closed)
            throw new ZaloDomainException(
                "Cannot add message to closed conversation");
        
        var message = new Message(type, content, direction, isFromBot);
        _messages.Add(message);
        
        AddDomainEvent(new MessageReceivedDomainEvent(Id, message.Id, content));
    }
    
    public void Close()
    {
        if (Status == ConversationStatus.Closed) return;
        
        Status = ConversationStatus.Closed;
        EndedAt = DateTime.UtcNow;
        
        AddDomainEvent(new ConversationClosedDomainEvent(Id));
    }
}
```

**Business Rules**:
- Messages cannot be added to closed conversations
- Conversation status must be Active or Closed
- Messages are immutable once created

---

### 2. ZaloCustomer Aggregate

Manages customer profiles and segmentation.

```csharp
public class ZaloCustomer : Entity, IAggregateRoot
{
    private readonly List<Tag> _tags = new();
    
    public string ZaloUserId { get; private set; }
    public CustomerProfile Profile { get; private set; }
    public CustomerSegment Segment { get; private set; }
    public DateTime FirstInteractionAt { get; private set; }
    public DateTime LastInteractionAt { get; private set; }
    public IReadOnlyCollection<Tag> Tags => _tags.AsReadOnly();
    
    public void UpdateProfile(string displayName, string avatarUrl, 
        string? phoneNumber = null, string? email = null)
    {
        if (string.IsNullOrWhiteSpace(displayName))
            throw new ArgumentException("Display name cannot be empty");
        
        Profile = new CustomerProfile(displayName, avatarUrl, phoneNumber, email);
        AddDomainEvent(new CustomerProfileUpdatedDomainEvent(Id, ZaloUserId));
    }
    
    public void AddTag(string tagName)
    {
        if (_tags.Any(t => t.Name.Equals(tagName, 
            StringComparison.OrdinalIgnoreCase)))
            return;
        
        _tags.Add(new Tag(tagName));
    }
    
    public void UpdateSegment(int conversationCount, int messageCount)
    {
        Segment = (conversationCount, messageCount) switch
        {
            (> 50, > 500) => CustomerSegment.VIP,
            (> 20, > 200) => CustomerSegment.Active,
            (> 5, > 50) => CustomerSegment.Regular,
            _ => CustomerSegment.New
        };
    }
}
```

**Business Rules**:
- ZaloUserId must be unique
- DisplayName is required
- Auto-segmentation based on engagement metrics
- No duplicate tags per customer

---

### 3. ChatbotRule Aggregate

Defines automation rules for chatbot responses.

```csharp
public class ChatbotRule : Entity, IAggregateRoot
{
    private readonly List<RuleCondition> _conditions = new();
    
    public string Name { get; private set; }
    public RuleType Type { get; private set; }
    public IReadOnlyCollection<RuleCondition> Conditions => _conditions.AsReadOnly();
    public RuleAction Action { get; private set; }
    public int Priority { get; private set; }
    public bool IsActive { get; private set; }
    
    public bool Evaluate(string userMessage)
    {
        if (!IsActive) return false;
        
        return Type switch
        {
            RuleType.Keyword => _conditions.Any(c => 
                userMessage.Contains(c.Value, StringComparison.OrdinalIgnoreCase)),
            RuleType.Regex => _conditions.Any(c => 
                Regex.IsMatch(userMessage, c.Value)),
            _ => false
        };
    }
    
    public void RecordMatch()
    {
        MatchCount++;
        LastMatchedAt = DateTime.UtcNow;
    }
}
```

**Business Rules**:
- Rules must have at least one condition
- Priority must be 0-100
- Higher priority rules evaluated first
- Inactive rules are skipped

---

### 4. MessageTemplate Aggregate

Manages ZNS templates.

```csharp
public class MessageTemplate : Entity, IAggregateRoot
{
    private readonly List<TemplateParameter> _parameters = new();
    
    public string ZaloTemplateId { get; private set; }
    public string Name { get; private set; }
    public string Content { get; private set; }
    public TemplateStatus Status { get; private set; }
    public IReadOnlyCollection<TemplateParameter> Parameters => _parameters.AsReadOnly();
    
    public Dictionary<string, string> ValidateParameters(
        Dictionary<string, string> providedParams)
    {
        var missingParams = _parameters
            .Where(p => p.IsRequired && !providedParams.ContainsKey(p.Name))
            .Select(p => p.Name)
            .ToList();
        
        if (missingParams.Any())
            throw new ZaloDomainException(
                $"Missing required parameters: {string.Join(", ", missingParams)}");
        
        return providedParams;
    }
}
```

---

## Value Objects

### CustomerProfile

```csharp
public class CustomerProfile : ValueObject
{
    public string DisplayName { get; }
    public string AvatarUrl { get; }
    public string? PhoneNumber { get; }
    public string? Email { get; }
    
    protected override IEnumerable<object?> GetEqualityComponents()
    {
        yield return DisplayName;
        yield return AvatarUrl;
        yield return PhoneNumber;
        yield return Email;
    }
}
```

### RuleCondition

```csharp
public class RuleCondition : ValueObject
{
    public string Field { get; }
    public string Operator { get; }
    public string Value { get; }
}
```

### RuleAction

```csharp
public class RuleAction : ValueObject
{
    public ActionType ActionType { get; }
    public string? ResponseText { get; }
    public Guid? TemplateId { get; }
}
```

---

## Enumerations

```csharp
public enum ConversationStatus { Active = 0, Closed = 1 }
public enum MessageType { Text = 0, Image = 1, Link = 2, Sticker = 3, Audio = 4 }
public enum MessageDirection { Incoming = 0, Outgoing = 1 }
public enum CustomerSegment { New = 0, Regular = 1, Active = 2, VIP = 3 }
public enum RuleType { Keyword = 0, Regex = 1, Intent = 2 }
public enum ActionType { SendText = 0, SendTemplate = 1, ForwardToHuman = 2 }
```

---

**Document Version**: 1.0  
**Last Updated**: 2026-01-18
