Business logic modules containing domain and data layers
Component modules encapsulate business logic for specific features. Each component module contains both the domain layer (business rules, entities, use cases) and the data layer (repository implementations, DTOs, data sources).
The domain layer contains no framework dependencies and represents pure business logic. The data layer implements domain contracts using specific technologies (HTTP, cache, etc.).
Implements cart persistence using the cache module:
class RealCartRepository( private val cacheProvider: CacheProvider) : CartRepository { // Implementation uses FlowCachedObject for reactive cart updates // Serializes cart data as JsonCartCacheDto}
class CartComponentAssembler( private val cacheProvider: CacheProvider, private val getUser: GetUser) { private val cartRepository by lazy { RealCartRepository(cacheProvider) } val updateCartItem: UpdateCartItem by lazy { UpdateCartItemUseCase(getUser, cartRepository) } val observeUserCart: ObserveUserCart by lazy { ObserveUserCartUseCase(getUser, cartRepository) } val addCartItem: AddCartItem by lazy { AddCartItemUseCase(getUser, cartRepository, updateCartItem) }}
The CartComponentAssembler uses lazy initialization to avoid creating unnecessary instances. Dependencies are provided via constructor injection.
data class Money(val amount: Double, val currencySymbol: String)
This is a pure domain module with no data layer - it only contains the domain model used by other components like cart-component and product-component.
class ProductComponentAssembler( private val httpClient: HttpClient) { private val productRepository by lazy { RealProductRepository(httpClient) } val getProducts by lazy { GetProducts(productRepository::getProducts) }}
The ProductComponentAssembler depends on HttpClient from the httpclient module, demonstrating how components can depend on library modules.
class RealUserRepository( private val httpClient: HttpClient, private val cacheProvider: CacheProvider) : UserRepository { // Login via HTTP, cache user data locally // Provide access to cached user data}
class UserComponentAssembler( private val httpClient: HttpClient, private val cacheProvider: CacheProvider) { private val userRepository by lazy { RealUserRepository(httpClient, cacheProvider) } val login: Login by lazy { LoginUseCase(userRepository) } val getUser by lazy { GetUser(userRepository::getUser) } val isUserLoggedIn by lazy { IsUserLoggedIn(userRepository::isLoggedIn) }}
The UserComponentAssembler is used by other components (like cart-component and wishlist-component) to get the current user.
class RealWishlistRepository( private val cacheProvider: CacheProvider) : WishlistRepository { // Uses FlowCachedObject for reactive updates // Serializes as JsonWishlistCacheDTO}
class WishlistComponentAssembler( private val cacheProvider: CacheProvider, private val getUser: GetUser) { private val wishlistRepository by lazy { RealWishlistRepository(cacheProvider) } val addToWishlist: AddToWishlist by lazy { AddToWishlistUseCase(getUser, wishlistRepository) } val removeFromWishlist: RemoveFromWishlist by lazy { RemoveFromWishlistUseCase(getUser, wishlistRepository) } val observeUserWishlist: ObserveUserWishlist by lazy { ObserveUserWishlistUseCase(getUser, wishlistRepository) } val observeUserWishlistIds: ObserveUserWishlistIds by lazy { ObserveUserWishlistIdsUseCase(getUser, wishlistRepository) }}
Keep Domain Layer PureThe domain layer should contain no Android or framework dependencies. Use only Kotlin standard library and Kotlin coroutines.
Use Value ObjectsFor domain concepts with validation logic (like Email, Password), create value objects that encapsulate the validation.
Repository PatternAlways define repository interfaces in the domain layer and implementations in the data layer. This keeps the domain independent of data sources.
Functional InterfacesUse fun interface for use cases with a single operation. This enables clean lambda syntax and makes testing easier.