Enterprise Level Application Architecture

Enterprise Level Application Architecture

In Android development, Data, Domain, and Presentation layers are often used to organize code and follow clean architecture principles. This approach enhances maintainability, scalability, and testability of your codebase.

Below is a detailed explanation of each layer in the Data-Domain-Presentation architecture:

1. Presentation Layer

This is the UI layer where the user interacts with the app.

Role:

  • Handles user input and displays data to the user.

  • Contains Android-specific components like Activity, Fragment, ViewModel, Jetpack Compose, or XML layouts.

Components:

  • UI Components:

    • Activity/Fragment: The main entry points for UI.

    • Jetpack Compose Views: Modern, declarative UI design.

    • XML Layouts: Traditional way to design UI.

  • ViewModel (from MVVM):

    • Acts as a bridge between the UI and Domain layers.

    • Holds and manages UI-related data in a lifecycle-conscious way.

    • Observes changes in data via LiveData, StateFlow, or Flow.

Example:

// Presentation Layer - ViewModel
class ProductViewModel(private val getProductsUseCase: GetProductsUseCase) : ViewModel() {
    private val _products = MutableLiveData<List<Product>>()
    val products: LiveData<List<Product>> get() = _products

    fun fetchProducts() {
        viewModelScope.launch {
            val result = getProductsUseCase()
            _products.postValue(result)
        }
    }
}

Key Points:

  • Stateless UI: UI components just display the data they receive.

  • Delegate business logic to the Domain layer and data retrieval to the Data layer.


2. Domain Layer

This is the business logic layer. It is independent of the Android framework.

Role:

  • Contains business logic and rules for the app.

  • Interacts with the Data layer and Presentation layer.

  • Facilitates clean separation by defining Use Cases.

Components:

  • Use Cases (Interactors):

    • Each use case performs a single operation (e.g., GetProductsUseCase).

    • Takes input, processes it, and returns output (data).

  • Entities:

    • Plain data objects (POJOs) that represent the core business models.

Example:

// Domain Layer - Use Case
class GetProductsUseCase(private val productRepository: ProductRepository) {
    suspend operator fun invoke(): List<Product> {
        return productRepository.getProducts()
    }
}

// Domain Layer - Entity
data class Product(
    val id: String,
    val name: String,
    val price: Double
)

Key Points:

  • Does not depend on Android SDK.

  • Promotes reusability and easy testing.


3. Data Layer

This is the data management layer. It handles data retrieval, storage, and network operations.

Role:

  • Provides data to the Domain layer.

  • Fetches data from:

    • Remote Sources (e.g., REST API, Firebase).

    • Local Sources (e.g., Room Database, SharedPreferences).

  • Implements Repository Pattern to abstract data sources.

Components:

  • Repository:

    • Mediates between data sources and domain logic.

    • Decides whether to fetch data from remote or local sources.

  • Data Sources:

    • RemoteDataSource: API calls using libraries like Retrofit or OkHttp.

    • LocalDataSource: Database operations using Room or SQLite.

  • Models:

    • DTOs (Data Transfer Objects) or entities for data layers.
  • Mappers:

    • Convert data models (e.g., DTOs) into domain entities.

Example:

// Data Layer - Repository
class ProductRepositoryImpl(
    private val remoteDataSource: RemoteDataSource,
    private val localDataSource: LocalDataSource
) : ProductRepository {
    override suspend fun getProducts(): List<Product> {
        return try {
            val productsDto = remoteDataSource.fetchProducts()
            productsDto.map { it.toDomain() }
        } catch (e: Exception) {
            localDataSource.getCachedProducts()
        }
    }
}

// RemoteDataSource
class RemoteDataSource(private val apiService: ApiService) {
    suspend fun fetchProducts(): List<ProductDto> {
        return apiService.getProducts()
    }
}

// DTO and Mapper
data class ProductDto(val id: String, val title: String, val price: Double)

fun ProductDto.toDomain() = Product(id, title, price)

Key Points:

  • Repository abstracts data sources from the Domain layer.

  • Mappers convert between layers (e.g., DTO → Domain Entity).

  • Focuses on network handling, caching, and other data operations.


Architecture Diagram:

Presentation Layer (UI)
   └── ViewModel → Domain Layer (Use Cases)
                           └── Repository → Data Layer (Remote/Local Data Sources)

Benefits of This Architecture:

  1. Separation of Concerns: Each layer has a single responsibility.

  2. Testability: You can unit test each layer independently.

  3. Scalability: Easy to add new features without affecting other layers.

  4. Maintainability: Clean structure reduces coupling and makes refactoring easier.

  5. Reusability: Domain layer logic can be reused across different apps or platforms.


Tech Stack:

LayerTechnologies
PresentationJetpack Compose, ViewModel, LiveData, Flow
DomainKotlin Coroutines, Plain Kotlin Classes
DataRetrofit, OkHttp, Room, Firebase, Hilt

Github Repo: https://github.com/shaikhsiddik/EnterpriseArchitecture