Project architecture based on CLEAN architecture with feature based package structure. This approach helps to retain code consistency for a large code base and provides ability for splitting up project in modules.
Code consists of 6 layers:
The dependency structure is following:
For now all layers lay down in the same gradle module but there are plans to split them between distinct gradle modules after code migration will be finished.
In code each layer represented by root package.
Layers package consists basically consists of base
package with some base code and feature packages along with some layer specific packages. base
package should have the same structure as the feature packages of this level.
Feature package of current level can depend on another feature packages of this level and on feature packages of levels that current level depends on.
Example:
class CourseInteractor
@Inject
constructor(
CourseRepository, // course feature package
LessonRepository // lesson feature package of domain level
)
You'll see a lot of models and mappers in this architecture but you shouldn't use all of them if you don't need to. For example if you have object like this
class Data(
@SerializedName("name")
val name: String
)
that came from API in the same way, can be stored to db without changes and passed to view you can declare it in domain layer and skip all mappers and models on other layers.
Cache layer contains everything you need to work with local db storage. All packages are optional as the layer itself. So you should not create cache level for feature with no caching logic and in the same way you don't need to create mapper package or model package if data layer model can be stored without changes.
Cache data source implementation connects cache
layer with data
layer.
Naming convention: feature name + CacheDataSourceImpl
suffix
Example: VideoCacheDataSourceImpl
Package with dao implementations for each object.
Naming convention: object name + EntityDao
suffix
Example: VideoEntityDao
Package with mappers to map from data layer models to cache models
Naming convention: object name + EntityMapper
suffix
Example: VideoEntityMapper
, CourseEntityMapper
Package with pojo classes that represents database entities.
Naming convention: object name + Entity
suffix
Example: VideoEntity
, VideoUrlEntity
, CourseEntity
Package with table schemes constants
Naming convention: object name + DbScheme
In global context remote layer serves the similar role to cache layer – it's another data source. remote
package has the same structure with feature base package and base
package.
Remote data source implementation connects remote
layer with data
layer.
Naming convention: feature name + RemoteDataSourceImpl
suffix
Example: VideoRemoteDataSourceImpl
Package with mappers to map from remote models to data layer models
Naming convention: object name + Mapper
suffix
Example: VideoMapper
, CourseMapper
Package with pojo classes that represents request structures and will be parsed with gson.
Naming convention: object name + Request
/Response
suffix
Example: CourseRequest
, EnrolmentRequest
, CourseResponse
Package with retrofit services interfaces.
Naming convention: feature name + Service
suffix
Example: EnrolmentService
Main goal of data layer is to manages data within available sources.
Package with mappers to map from data models to domain layer models
Naming convention: object name + Mapper
suffix
Example: VideoMapper
, CourseMapper
Package with pojo classes that represents data layer models.
Naming convention: object name
Example: Course
, Enrolment
Package with repositories interfaces implementations. Repository contains logic that manages data between sources.
Naming convention: feature name + RepositoryImpl
suffix
Example: CourseRepositoryImpl
Package with source interfaces.
Naming convention: feature name + source type + DataSource
suffix
Example: CourseRemoteDataSource
, CourseCacheDataSource
Domain layer contains all business logic of application.
Package with bussiness logic exceptions. Those exceptions could be thrown from domain
, data
or sources levels and should be handled in presentation
level.
Naming convention: case name + Exception
suffix
Example: NotAuthorizedException
Package with bussiness logic. Interactor should unite common case logic. Interactor could be splat up in disctinct interactors that carries less logic.
Naming convention: case name + Interactor
suffix
Example: CourseInteractor
, CourseEnrolmentInteractor
, CourseBillingInterctor
Package with mappers to map data from another domain packages.
Naming convention: target object name + Mapper
suffix
Example: CourseContentMapper
, CourseInfoMapper
Package with pojo classes that represents domain layer models.
Naming convention: object name
Example: CourseContent
, CourseInfo
, EnrolmentStatus
Package with repositories interfaces.
Naming convention: object name + Repository
suffix
Example: CourseRepository
Package with different resolvers and helpers in order to simplify interactors in some cases.
Naming convention: case name + Resolver
suffix
Example: DeadlinesResolver
Presentation layer connects view
level with domain
level and manages view states.
Package with mappers to map data from domain or presentation layers. This can be useful to map view states.
Naming convention: target object name + Mapper
suffix
Example: CourseContentItemsMapper
Package with pojo classes that represents models which is used in view states.
Naming convention: object name
Example: DeadlineState
Presenter that manages current view state and fetches data from interactors.
Naming convention: feature name + Presenter
suffix
Example: CoursePresenter
View contract.
Naming convention: feature name + View
suffix
Example: CourseView
View is the last layer in this architecture which consists of platform related implementations of displaying content. In global terms this layer characterize application. Along with view implementations it contains DI components needed to build application.
Injection package contains everything related to dependency injection as components, modules and scopes. It structured in the same way as layer package with base
package and feature packages. Usually feature package consists of:
FeautureNameDataModule
– module that provides data layer of feature (if exists) with repository implementationFeatureNameModule
– module with presenters bindings and other feauture related componentsFeatureNameRoutingModule
– module with feature specific routersFeatureNameScope
– scope of feauture if neededFeatureNameComponent
– component of feauture if neededUsually there are a lot of features that have only FeatureNameDataModule
that is included in another components.
Package with mappers to map data from domain or presentation layers to view level models.
Naming convention: target object name + Mapper
suffix
Example: CourseContentItemsMapper
Package with pojo classes that represents models which is used in views, like RecyclerView
adapter items and etc.
Naming convention: object name
Example: DeadlineState
Package with routers specific to current feature, like branch
routers or deeplink routers.
Naming convention: feature name + case name + Router
Example: CourseDeeplinkRouter
TBD
Package with everything related to ui like activities, fragments, adapters, delegates, views etc.
If after reading the previous paragraph you still have question: "Where should I put that code?" just keep in mind that code structure should be easy splittable by layers and by features.
As can be seen from this diagram code base is easy splittable and each code block can be extended to distinct gradle module and be reused in different target or application. Dashed arrows means optional dependencies.