A while ago we published this article on how we were trying to tidy up the Android project for the Buffer for Android app: https://overflow.buffer.com/2016/09/26/android-rethinking-package-structure/.

However, our project is now moving towards a modularised approach so our package structure is changing (but also staying slightly the same). We wanted to keep the package-by-feature approach in place so we can continue to benefit from its advantages such as it being super easy to navigate around the project. 

We recently released two boilerplate projects for clean architecture and during the creation of these, it was a great chance to experiment with how we would package our projects from now on when using a modularised approach. The project structure for the modules looks like this:

Whilst this approach has received positive responses in the community, I received a couple of emails direct messages asking about how to package by feature when using clean architecture (or in fact, any approach that involves modularising your project) when it comes to Android apps. If you haven’t seen these boilerplates yet, then you can do so here:

https://github.com/bufferapp/android-clean-architecture-boilerplate

https://github.com/bufferapp/clean-architecture-components-boilerplate

Because of these questions, I felt it would be the right thing to do to write a short piece on how we can still package our projects by feature even when modularising our applications. And you know what the great thing is? It’s not even a complicated process or even that different from how we approach package-by-feature in single module projects. So with that in mind, let’s dive in 🙂

Note: This article will be packaging our features with the concept of Clean Architecture in mind, but the approach can still be applied to other modularised approaches.


Domain Layer

The Domain Layer is the central point of our modularised project it houses our business objects and contains the Use Case classes used to interact with these from other layers. As well as these, it also defines the interface used by the Use Cases to retrieve these from an out layer which is to implement it. The Domain layer in a current project we are working on looks something like this (some features have been removed from the structure for simplicities sake):

Within the Domain layer, the intention is to package everything by the feature that it belongs to. Here, Conversations is a feature of our application (this may be the retrieving of all conversations or maybe even a single one). If we were to have other features here, then they would be in their own feature package, for example; Message, Settings, Account would all be appropriate kinds of features to package under. Now whenever I need to tweak something within the Domain layer of the project, I can jump straight into the feature that I am looking to amend.

Within the Conversations feature, we then go ahead and package classes accordingly. So within this package, we have:

  • A  Model package to contain all of the model classes that are related to conversations
  • A Repository package which contains the interface our Conversation Use Case classes use to manage operations related to the models in this package (the Data layer will provide an implementation of this interface)

Within the Conversation package, we will also have the different Use Cases which are related to Conversation operations. If you only have one or two use cases, these don’t necessarily need to live within a package but if your Use Cases grow then it would make sense to package these up appropriately.

Outside of these feature packages we then have the Executor package. This contains abstractions for the management of Threading within our application. You may not have this so this package would be redundant if so.

Why is packaging the Domain layer by feature necessary? Well, imagine if we had 8 different features that weren’t packaged by feature… having an Interactor directory that contained the Use Case classes for 8 different features would get quite difficult to navigate over time. The same applies to mixing all of the Models or Repository interfaces into generic Model / Repository packages the time taking for navigational actions adds up over time, it’s much easier on the mind to keep these in an organised fashion.


Data Layer

The Data Layer is the central point of access for Data within our modularised project it houses the implementation for the data access interface defined in the Domain layer, as well as the repository interfaces to be used by the data source implementations for outer layers. The Data layer in a current project we are working on looks something like this (some features have been removed from the structure for simplicities sake):

Within the Data layer, the intention is again to package everything by the feature that it belongs to. Here, we see the Conversations feature again which has the same purpose of packaging up everything conversation related within the Data layer. Again, if I need to amend something within the Data layer it is incredibly easy to find what I need to change no hunting around for that file!

Within the Conversations feature, we then go ahead and package classes accordingly. So within this package of the Data layer we have:

  • A Model package to contain all of the model classes that are related to conversations. These are this layers representation of the Models
  • A Mapper package which contains the mappers used to map this layers Model implementations to and from the Model defined found with the Domain layer
  • A Repository package which contains the interfaces for outer data layers (such as Cache and Remote) to implement. This allows us to enforce the operations that are required and abstract the implementation. it will also contain a Data Store interface which is used to enforce the operations to be implemented by this layers local/remote data store
  • A Source package which contains the implementation for the data store interfaces for the data store interface defined within the Repository package. This approach allows us to define the same operations to be implemented by all sources from a single interface. The package will also contain a Factory class to create the desired Data Store class.

Within the Conversation package, we will also have a Repository implementation for the interface which we defined within the Domain layer. This is the implementation which will be used during the data operations for our Use Case classes. There will only be one of these per feature package, so it’s not necessary to put it into its own package.

Outside of these feature packages we then have the Executor package. This contains the implementations for the management of Threading defined within the Domain layer. You may not have this so this package would be redundant if so.

Why is packaging the Data layer by feature necessary? Again, imagine if we had 8 different features that weren’t packaged by feature. having a Source directory that contained the Data Store implementations for 8 different features would get quite difficult to navigate over time. The same applies to mixing all of the Models or Mapper classes into generic Model / Repository packages, we want to focus on creating over searching 🙂


Remote Layer

The Remote Layer contains the implementation for the remote data source within our modularised project this houses the implementation for the remote data store interface defined in the Data layer. The Remote layer in a current project we are working on looks something like this (some features have been removed from the structure for simplicities sake):

Within the Remote layer, the intention is again to package everything by the feature that it belongs to. Here, we again see the Conversations feature again which has the same purpose of packaging up everything conversation related within the Remote layer.

Within the Conversations feature, we then go ahead and package classes accordingly. So within this package of the Remote layer we have:

  • A Model package to contain all of the model classes that are related to conversations. These are this layers representation of the Models
  • A Mapper package which contains the mappers used to map this layers Model implementations to the Model defined found with the Data layer

Within the Conversation package, we will also have a Repository implementation for the interface which we defined within the Data layer. This is the implementation which will be used to provide the operations required for fetching the data from the remote source. There will only be one of these per feature package, so it’s not necessary to put it into its own package.

Outside of these feature packages we then have the Service package. This contains the Retrofit interface we use to carry out our network operations, as well as a factory class used for creating an instance of our API service.

Why is packaging the Remote layer by feature necessary? At this point, I am hoping that I don’t need to back up my argument again but I hope by now you can see the benefits of this!


Cache Layer

The Cache Layer contains the implementation for the local data source within our modularised project this houses the implementation for the cache data store interface defined in the Data layer. The Cache layer in a current project we are working on looks something like this (some features have been removed from the structure for simplicities sake):

Within the Cache layer, the intention is again to package everything by the feature that it belongs to. Here, we again see the Conversations feature again which has the same purpose of packaging up everything conversation related within the Cache layer.

Within the Conversations feature, we then go ahead and package classes accordingly. So within this package of the Cache layer we have:

  • A Model package to contain all of the model classes that are related to conversations. These are this layers representation of the Models
  • A Mapper package which contains the mappers used to map this layers Model implementations to the Model defined found with the Data layer. 
  • A Dao package which contains the Room DAO implementations for the storing/retrieval of Conversation data
  • A Constants package which will contain a constants file for conversation queries and column names etc

Within the Conversation package, we will also have a Repository implementation for the interface which we defined within the Data layer. This is the implementation which will be used to provide the operations required for fetching the data from the local source. There will only be one of these per feature package, so it’s not necessary to put it into its own package.

Outside of these feature packages we then have the Database package. This contains the Room database implementation used for storing our local data and any other related database files (such as migrations)


Presentation Layer

Now if we hop back over to the other side of our Domain layer we can take a look at the structure of the Presentation layer. The Presentation Layer contains the presentation logic used within our modularised project this houses the implementation for the classes which will use the Use Cases from our Domain layer to retrieve and store data (this may be either Presenters or View Models). The Presentation layer in a current project we are working on looks something like this (some features have been removed from the structure for simplicities sake):

Within the Presentation layer, the intention is again to package everything by the feature that it belongs to. Here, we again see the Conversations feature again which has the same purpose of packaging up everything conversation related within the Presentation layer.

Within the Conversations feature, we then go ahead and package classes accordingly. So within this package of the Presentation layer we have:

  • A Model package to contain all of the model classes that are related to conversations. These are this layers representation of the Models
  • A Mapper package which contains the mappers used to map this layers Model implementations to the Model defined found with the Domain layer. 

Within the Conversation package, we will also have either our Presenter or View Model implementations. This is the implementation which will be used to communicate with the Domain layer and also pass the required data through to the View layer (and handle the presentation if using Presenters). There will only be one of these per feature package, so it’s not necessary to put it into its own package.

Outside of these feature packages we then have the Base package. This package will contain any Base classes used to enforce behaviour between implementations (such as a BasePresenter or BaseViewModel etc).


View Layer

The View Layer contains the views used within our modularised project this houses the activities/fragments/widgets which will use the classes from our Presentation layer to retrieve handle data (and presentation if using presenters). The Presentation layer in a current project we are working on looks something like this (some features have been removed from the structure for simplicities sake):

Within the View layer, the intention is again to package everything by the feature that it belongs to. Here, we again see the Conversations feature again which has the same purpose of packaging up everything conversation related within the Presentation layer.Within the Conversations feature, we then go ahead and package classes accordingly. So within this package of the View layer we have:

  • A Model package to contain all of the model classes that are related to conversations. These are this layers representation of the Models personally, we do not map again from the presentation layer as it is not entirely necessary. This is down to personal preference though 
  • A Mapper package which contains the mappers used to map this layers Model implementations to the Model defined found with the Presentation layer. This will only be necessary if you map to a model that is specific to this layer
  • A Widget package which houses widgets specific to this feature and not used elsewhere

Within the Conversation package, we will also have our View implementations (such as an activity or a fragment). This will then make use of the corresponding class from the presentation layer. There will only be one (or two if using an activity-fragment setup) of these per feature package, so it’s not necessary to put it into its own package

Outside of these feature packages we then have the Util and Widget packages. These packages will contain any utility classes and widgets which are used by multiple classes through the View layer. You’ll also notice the Injection package which houses our Dependency Injection configuration we currently bundle this into our View module, however you can separate this out if you feel that it is necessary.


Conclusion

Whether you’re using the same setup, a similar setup or you’re not using a modularised approach yet I hope that from this post you can see the benefits from packaging by feature regardless of your project structure. Personally, I find the two concepts go hand-in-hand modularised projects help to create a clear separation of concerns (which is important whether using clean architecture or not), they allow for a clear focus and for you to carry out smaller implementation tasks at a time. Package by feature compliments this separation of concerns even further by creating a clear and easy to navigate structure within each module.

We’d love to hear any thoughts or questions you may have on this and we’re happy to help should you have any questions 🙂

Free up your day with our Social Media Tools

Buffer can save you up to an hour a day and grow your traffic too.

Learn More
Written by Joe Birch

I’m an Android Engineer and I love creating beautiful, clean and functional applications that help to make peoples lives easier – I love to be constantly learning and I enjoy writing about Android things / working on open-source projects over on GitHub. I have a bit of a love for Android TV which I sometimes talk about at conferences too! I first found my passion for Android whilst I was at university and have been working with it non-stop since – I find it an amazing platform. I love how it’s constantly evolving, it’s open-ness and the community around it!

  • Pavel Shmakov

    It would be even better if splitting by feature was on the top level. Because first, there will be less jumping around while working on one feature, and second, more importantly, there would be no need to follow “clean architechure” for every feature, because for some it might be an overkill.

    • Joe Birch

      The thing is when projects grow past a certain size having everything with a single module can still get cluttered. You can package by feature in this way, maybe it’s just down to personal preference. However, whilst this article isn’t directed at instant-apps – some of the concepts here can be applied to the packaging when working in those cases.

      For some projects clean architecture will be overkill, but only for some. The modularisation in clean architecture actually helps to promote easy reuse due to the separation of concerns – so in some cases, it will actually make it easier to reuse components because they are already abstracted from where they may have originally been implemented 🙂

  • Francesco Noya

    I have some doubt when it comes to sub-packages of features package: It will force lot of methods/fields/classes to be declared as public (at least in Java), while if you only have features packages you will have bigger packages but more restricted accesses to the objects…

  • Kaloyan Roussev

    I do the same but inversed. My root directory has the features, and each feature has the view, network, db and core subpackages

  • Jorge Castillo Pérez

    Hi again Joe. Since we have been discussing some points about this article on Twitter, I’ll leave them here so other people can also get some benefit from the answers.

    1. About multiple Gradle modules for layer separation: As you know, I was working like this for Clean arch. for a long time, some time ago and I found some concerns about compilation performance and non always handy navigation overhead. Back then incremental compilation was not doing very good, and caching versions for non modified code / modules was not a real advantage. It wasn’t even working, tbh. It’s probably much better now but, do you have a strong preference to pick gradle module division over package division in terms of layers ?

    * IMO, in the end both provide the same boundaries, and you need to be aware by yourself on not breaking the dependency rule (you could always skip it if you wanted by putting things where they do not belong). And about navigation, we end up using IDE shortcuts anyways most of the time, so package / module division kind of loses some of its power, may be.

    2. Could be interesting to know about how is the direction of your dependencies between modules, cause I guess any module needs to depend on the domain one, for example. I guess all modules need to depend on domain except for the android one?
    – App module wouldn’t probably need to depend on it if your domain models are mapped to simpler “renderable” or “view state models” before reaching the view implementation.
    – Presentation needs to invoke use cases and retrieve domain models from those.
    – Data layer needs to map network / db or any other sources of data related DTOs to domain models before returning them. Also map errors / exceptions to controlled domain errors under a constrained hierarchy.

    3. What’s your policy on where to locate UI / unit / integration tests? do you have a single module wrapping all of them ? do segregate tests per module?. I tried having a single module with all the tests on some apps / companies but something didn’t work properly about module dependencies. I vaguely remember that it wasn’t possible to depend on all the Gradle modules from a single one (otherwise you couldn’t exercise on tests code from all parts of the app), since depending on the android app module from an external one was not possible at all. Not sure if that’s possible nowadays anyways to be honest.

    4. Where do you use interfaces? It uses to be a very interesting topic to discuss. Many people just likes to add interfaces everywhere, even if those are not really needed for mocking purposes. I think they feel better or something :D. Others (like me) rather just adding them when you need to abstract different switchable implementations at runtime, like a presenter that is able to play with 2 different view implementations following the same contract or also when it’s mandatory for not breaking dependency rule from Clean arch, even if sometimes it does not add as much value. As you know, mocking by extension is always possible so you don’t really need interfaces to have testable code. A good example of mocking by extensions would be Mockito itself.

    5. And last but not less important: You like to organize your packages by feature, so, where would you put a domain model or a piece of logic that can be shared between different features?

    Sorry for the TL;DR! Just feel free to answer whenever you want or not answer at all 😉

  • Vimal P

    You have defined injection package in View layer, and I believe it will not help us while writing Unit Tests. In my company we prefer to have dependency injection package in Presenter layer which will allow us to mock the objects when writing unit tests for the methods in Presenter, and more over we tend to keep the view layer dumb.