This chat application follows Clean Architecture principles with proper separation of concerns across three main layers.
┌─────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
│ │ ChatScreen │ │ ChatViewModel │ │ ChatTheme │ │
│ │ (Compose UI) │ │ (State) │ │ (Styling) │ │
│ └─────────────────┘ └─────────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ DOMAIN LAYER │
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
│ │ Use Cases │ │ Models │ │ Repositories │ │
│ │ (Business │ │ (Entities) │ │ (Interfaces) │ │
│ │ Logic) │ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ DATA LAYER │
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
│ │ Repository Impl │ │ Data Sources │ │ Entities │ │
│ │ (Concrete Impl) │ │ (In-Memory) │ │ (DTOs) │ │
│ └─────────────────┘ └─────────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
src/commonMain/kotlin/org/cmppractice/app/
├── domain/ # 🎯 DOMAIN LAYER
│ ├── model/
│ │ └── Message.kt # Domain entities
│ ├── repository/
│ │ └── MessageRepository.kt # Repository interface
│ └── usecase/
│ ├── GetMessagesUseCase.kt # Business logic
│ ├── SendMessageUseCase.kt
│ ├── ClearMessagesUseCase.kt
│ └── GenerateAutoResponseUseCase.kt
├── data/ # 💾 DATA LAYER
│ └── ChatRepository.kt # Repository implementation
├── ui/ # 🎨 PRESENTATION LAYER
│ ├── ChatScreen.kt # UI components
│ ├── ChatViewModel.kt # State management
│ ├── ChatTheme.kt # Styling
│ └── TypingIndicator.kt
├── di/ # 🔧 DEPENDENCY INJECTION
│ └── AppModule.kt # Dependency container
└── App.kt # Application entry point
Message: Core business entity representing a chat messageUser: Represents a chat participant
GetMessagesUseCase: Retrieves all messagesSendMessageUseCase: Handles message sending with validationClearMessagesUseCase: Clears all chat historyGenerateAutoResponseUseCase: Generates smart auto-responses
MessageRepository: Defines contract for data access
ChatRepositoryImpl: Concrete implementation using in-memory storageMessageEntity: Data transfer object for persistence layer
- Flow-based reactive data streams
- Entity-to-domain model mapping
- UUID-based message identification
ChatViewModel: Manages UI state using domain use cases- Reactive state management with StateFlow
- Clean separation from business logic
ChatScreen: Main chat interfaceMessageBubble: Individual message displayTypingIndicator: Animated typing indicator
Provides all dependencies following the dependency rule:
class AppModule {
// Data Layer
private val messageRepository: MessageRepository = ChatRepositoryImpl()
// Domain Layer (Use Cases)
private val getMessagesUseCase = GetMessagesUseCase(messageRepository)
private val sendMessageUseCase = SendMessageUseCase(messageRepository)
private val clearMessagesUseCase = ClearMessagesUseCase(messageRepository)
private val generateAutoResponseUseCase = GenerateAutoResponseUseCase()
// Presentation Layer
val chatViewModel: ChatViewModel = ChatViewModel(...)
}- Each layer has a single responsibility
- Business logic is isolated in the domain layer
- UI logic is separate from business logic
- Dependencies point inward (toward domain)
- Domain layer has no external dependencies
- Data and UI depend on domain abstractions
- Use cases can be tested independently
- Repository can be mocked for testing
- ViewModel tests don't require UI framework
- Changes in one layer don't affect others
- Easy to add new features or modify existing ones
- Clear structure makes code easier to understand
- Easy to add new use cases
- Can switch data sources without affecting business logic
- UI can be completely replaced without touching domain
- User Interaction → ChatScreen
- UI Event → ChatViewModel
- Business Logic → Use Cases
- Data Access → Repository Interface
- Data Storage → Repository Implementation
- Data Return → Flow back through layers
- UI Update → StateFlow triggers recomposition
This architecture makes it easy to add:
- Database persistence (just implement MessageRepository)
- Network communication (add network data source)
- User authentication (add auth use cases)
- Message encryption (add security use cases)
- File sharing (extend message domain model)
The clean separation ensures that adding these features won't require major refactoring of existing code.