The domain model is the heart of the TelegrmBot application. It encapsulates the business rules and ensures data integrity through entities and value objects that are framework-agnostic and technology-independent.
All domain classes are located in src/main/java/com/acamus/telegrm/core/domain/ and contain zero Spring or JPA annotations.
The updateLastMessageAt() method is a business rule. By encapsulating this logic in the entity, we prevent external code from incorrectly manipulating the timestamp.
Ensures only valid email addresses exist in the system.
// src/main/java/com/acamus/telegrm/core/domain/valueobjects/Email.javapackage com.acamus.telegrm.core.domain.valueobjects;import java.util.regex.Pattern;public record Email(String value) { private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})$"); public Email { Objects.requireNonNull(value, "Email cannot be null"); if (value.isBlank()) { throw new IllegalArgumentException("Email cannot be blank"); } if (!EMAIL_PATTERN.matcher(value).matches()) { throw new IllegalArgumentException("Invalid email format: " + value); } value = value.trim(); }}
Usage:
// This will compile and runEmail validEmail = new Email("[email protected]");// This will throw IllegalArgumentException at constructionEmail invalidEmail = new Email("not-an-email");
Java records provide automatic immutability, equals(), hashCode(), and toString() implementations. The compact constructor syntax allows validation logic.
// src/main/java/com/acamus/telegrm/core/domain/valueobjects/Password.javapackage com.acamus.telegrm.core.domain.valueobjects;public record Password(String value) { public Password { Objects.requireNonNull(value, "Password cannot be null"); if (value.isBlank()) { throw new IllegalArgumentException("Password cannot be blank"); } }}
In this implementation, the password is already hashed before being wrapped in the value object. Additional validation rules (length, complexity) could be added here.
// src/main/java/com/acamus/telegrm/core/domain/valueobjects/TelegramChatId.javapackage com.acamus.telegrm.core.domain.valueobjects;public record TelegramChatId(Long value) { public TelegramChatId { Objects.requireNonNull(value, "Telegram Chat ID cannot be null"); }}
Using TelegramChatId instead of Long prevents mixing up chat IDs with other Long values (user IDs, message IDs, etc.). The compiler enforces type safety.
// src/main/java/com/acamus/telegrm/core/domain/valueobjects/MessageContent.javapackage com.acamus.telegrm.core.domain.valueobjects;public record MessageContent(String value) { private static final int MAX_LENGTH = 4096; // Telegram's limit public MessageContent { Objects.requireNonNull(value, "Message content cannot be null"); if (value.isBlank()) { throw new IllegalArgumentException("Message content cannot be blank"); } if (value.length() > MAX_LENGTH) { throw new IllegalArgumentException( "Message content exceeds max length of " + MAX_LENGTH ); } }}
This value object enforces Telegram’s 4096 character limit at the domain level, catching violations early before attempting to send to the API.
You might wonder why Conversation doesn’t have a List<Message> field.Answer: In hexagonal architecture with repository pattern, we keep aggregates small:
Avoids loading all messages into memory
Messages are retrieved via MessageRepositoryPort.findByConversationId()
Better performance and scalability
The relationship exists logically, not as an in-memory collection.
Entities Have Identity: User, Conversation, Message are identified by ID
Value Objects Are Immutable: Records ensure immutability and validation
Factory Methods Control Creation: Private constructors + factory methods
Business Rules Are Encapsulated: Logic lives in the domain, not in services
Framework-Agnostic: Zero Spring, JPA, or external annotations
Type Safety Over Primitives: Custom types prevent errors
When designing new features, start by modeling the domain. Ask: “What are the entities? What are the rules? What can go wrong?” Build your domain model first, then add ports and adapters.