All code contributions should follow SOLID principles:
Single Responsibility Principle
Each class should have one, and only one, reason to change.Good Example:
// Each class has a single responsibilityclass GetProductsUseCase(private val repository: ProductRepository) { suspend operator fun invoke(query: String): Result<List<Product>> { if (query.isBlank()) return Result.success(emptyList()) return repository.searchProducts(query) }}
Bad Example:
// ❌ ViewModel doing network calls and mappingclass HomeViewModel { fun searchProducts(query: String) { val response = api.search(query) // ❌ Direct API call val products = response.map { ... } // ❌ Mapping in ViewModel }}
Open/Closed Principle
Classes should be open for extension but closed for modification.Good Example:
// Abstract base that can be extendedabstract class BaseRepository { protected suspend fun <T> safeApiCall( apiCall: suspend () -> Response<T> ): Result<T> { /* ... */ }}
Liskov Substitution Principle
Derived classes must be substitutable for their base classes.Good Example:
interface ProductRepository { suspend fun searchProducts(query: String): Result<List<Product>>}class ProductRepositoryImpl(private val api: MeliApi) : ProductRepository { override suspend fun searchProducts(query: String): Result<List<Product>> { // Implementation maintains the contract }}
Interface Segregation Principle
Clients should not be forced to depend on interfaces they don’t use.Good Example:
// Separate focused interfacesinterface ProductReader { suspend fun getProduct(id: String): Result<Product>}interface ProductSearcher { suspend fun searchProducts(query: String): Result<List<Product>>}
Dependency Inversion Principle
Depend on abstractions, not concretions.Good Example:
// ViewModel depends on abstraction (interface)class HomeViewModel @Inject constructor( private val getProductsUseCase: GetProductsUseCase // ✓ Use case abstraction) : ViewModel()// ❌ Bad: Depending on concrete implementationclass HomeViewModel( private val repository: ProductRepositoryImpl // ❌ Concrete class)
Follow the testing patterns established in the codebase:
1
Unit Tests for All Layers
Create unit tests for repositories, use cases, and ViewModels:
class ProductRepositoryImplTest { private val api: MeliApi = mockk() private val repository = ProductRepositoryImpl(api) @Test fun `searchProducts returns success when api succeeds`() = runTest { // Test implementation }}
2
Use MockK for Mocking
Use MockK instead of Mockito for Kotlin-friendly mocking:
feat: add product filtering by category- Implement category filter in ProductRepository- Add FilterProductsUseCase- Update HomeViewModel to support filters- Add UI components for category selectionCloses #123
When adding new features, update the documentation:
Code Comments: Use KDoc for public APIs
README: Update if architecture changes
Migration Guides: For breaking changes
API Documentation: For new endpoints
Example KDoc:
/** * Repository for managing product data from Mercado Libre API. * * This repository handles product search and detail retrieval, * including DTO to domain model mapping and error handling. * * @property api The Mercado Libre API service */class ProductRepositoryImpl @Inject constructor( private val api: MeliApi) : ProductRepository { /** * Searches for products matching the given query. * * @param query Search term to filter products. If blank, returns empty list. * @return Result containing list of products or error */ override suspend fun searchProducts(query: String): Result<List<Product>> { // Implementation }}