Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Powering Modularization at Airbnb: Plugin Archi...

adellhk
November 26, 2019

Powering Modularization at Airbnb: Plugin Architecture

At Airbnb we prize modularization for helping us to drive code ownership and decrease build times. Unfortunately, these benefits also bring increased dependency management complexity. Our Plugin architecture, building intermediate annotations for Dagger 2, helps us to improve that.

adellhk

November 26, 2019
Tweet

Other Decks in Technology

Transcript

  1. rowController {searchResult -> when { buildHelper.isChairingEconomy() -> { ChairshareRow(searchResult) //chairshare/ChairshareRow.kt

    CornercapRow(searchResult) //cornercaps/CornecapRow.kt } buildHelper.isThroneIron() -> { IroningRow(searchResult) } buildHelper.is….n() -> { //and repeat... } } rowController {searchResult -> ChairshareRow(searchResult) //chairshare.ChairshareRow.kt CornercapRow(searchResult) //cornercaps.CornecapRow.kt }
  2. A team can provide infrastructure which depends on contributions from

    consumers, without depending on the consumers
  3. //com.chairshare.searchrow/build.gradle implementation project(':base') //com.chairshare.searchfeature/build.gradle implementation project(':base') implementation project(':searchrow') //com.chairshare.chairsharefeature/build.gradle implementation

    project(':base') implementation project(':searchrow') //com.chairshare.cornercapsfeature/build.gradle implementation project(':base') implementation project(':searchrow') //com.chairshare.cleaningfeature/build.gradle implementation project(':base') implementation project(':searchrow') //lib.search interface SearchRow //feat.chairshare class ChairshareRow @Inject constructor(searchResult: SearchResult) : SearchRow {} @Provides @IntoMap fun provideChairshareRow(searchResult: SearchResult): SearchRow { return ChairshareRow(searchResult) } //feat.search class DefaultSearchRow(searchResult: SearchResult) : SearchRow {} class SearchFragmentListAdapter @Inject constructor( var searchResults: List<SearchResult>, val searchRows: Map<@JvmSuppressWildcards String, @JvmSuppressWildcards SearchRow> ) : RecyclerView.Adapter<AdapterSearchRow>() { override fun onBindViewHolder(holder: AdapterSearchRow, position: Int) { val searchResult = searchResults[position] val searchRow = searchRows.getOrDefault( searchResult.type, DefaultSearchRow(searchResult) ) holder.bind(searchRow) } } //com.chairshare.lib.search/build.gradle implementation project(':base') //com.chairshare.feat.search/build.gradle implementation project(':base') implementation project(':lib.search') //com.chairshare.feat.chairshare/build.gradle implementation project(':base') implementation project(':lib.search') //com.chairshare.feat.cornercaps/build.gradle implementation project(':base') implementation project(':lib.search') //com.chairshare.feat.cleaning/build.gradle implementation project(':base') implementation project(':lib.search') Attributes enforced pre-commit: • Contents • Prefix • Dependencies Ex: • feature • library
  4. //feat.chairshare @Provides @IntoMap fun provideChairshareRow(searchResult: SearchResult) = ChairshareRow(searchResult) //feat.search class

    SearchFragmentListAdapter @Inject constructor( var searchResults: List<SearchResult>, val searchRows: Map<String, SearchRow> ) : RecyclerView.Adapter<AdapterSearchRow>() { override fun onBindViewHolder(holder: AdapterSearchRow, position: Int) { val searchResult = searchResults[position] val searchRow = searchRows.getOrDefault( searchResult.type, DefaultSearchRow(searchResult) ) holder.bind(searchRow) } } //feat.chairshare @Provides @IntoMap fun provideChairshareRow(searchResult: SearchResult): SearchRow = ChairshareRow(searchResult) //feat.search class SearchFragmentListAdapter @Inject constructor( var searchResults: List<SearchResult>, val searchRows: Map<String, SearchRow> ) : RecyclerView.Adapter<AdapterSearchRow>() { override fun onBindViewHolder(holder: AdapterSearchRow, position: Int) { val searchResult = searchResults[position] val searchRow = searchRows.getOrDefault( searchResult.type, DefaultSearchRow(searchResult) ) holder.bind(searchRow) } } Bonus quiz
  5. //lib.search @PluginPoint interface SearchRowPluginPoint{ fun getSearchRows(): Map<String, SearchRow> } //feat.chairshare

    @Plugin(pluginPoint = SearchRowPluginPoint::class) class ChairshareRow @Inject constructor(searchResult: SearchResult) : SearchRow //feat.search class SearchFragmentListAdapter @Inject constructor( var searchResults: List<SearchResult>, val searchRows: Map<String, SearchRow> ) : RecyclerView.Adapter<AdapterSearchRow>() { override fun onBindViewHolder(holder: AdapterSearchRow, position: Int) { val searchResult = searchResults[position] val searchRow = searchRows.getOrDefault( searchResult.type, DefaultSearchRow(searchResult) ) holder.bind(searchRow) } }
  6. //lib.search @PluginPoint interface SearchRowPluginPoint{ fun getSearchRows(): Map<String, SearchRow> } //feat.chairshare

    @Plugin(SearchRowPluginPoint::class) class ChairshareRow @Inject constructor(searchResult: SearchResult) : SearchRow //step three: use the Plugin(s) and PluginPoint by injecting the set //feat.search class SearchFragmentListAdapter @Inject constructor( var searchResults: List<SearchResult>, val searchRows: Map<String, SearchRow> ) : RecyclerView.Adapter<AdapterSearchRow>() { override fun onBindViewHolder(holder: AdapterSearchRow, position: Int) { val searchResult = searchResults[position] val searchRow = searchRows.getOrDefault( searchResult.type, DefaultSearchRow(searchResult) ) holder.bind(searchRow) } } AP Rounds Yo dawg... //During compilation(annotation processing, we convert @Plugin and @PluginPoint into Dagger annotations like @Provides @IntoSet). The below is generated by our PluginAnnotationProcessor @Provides @IntoMap fun provideChairShareRow(searchResult: SearchResult) = ChairshareRow(searchResult)
  7. @Suppress("Detekt.UtilityClassWithPublicConstructor") @DaggerDeclaration(singletonScope = AirSingleton::class) class ChairshareFeatDagger { @ComponentScope @Subcomponent(modules =

    [ChairshareFeatDagger.ChairshareFeatModule::class]) interface ChairshareFeatComponent : BaseGraph, FreshScope { @Subcomponent.Builder interface Builder : SubcomponentBuilder<ChairshareFeatComponent> { override fun build(): ChairshareFeatDagger.ChairshareFeatComponent } } @Module abstract class ChairshareFeatModule { @Module companion object } @Module(includes = [ChairshareFeatInfraModule::class]) @AppModuleDeclaration abstract class AppModule { @Module companion object } @AppGraphDeclaration interface AppGraph : BaseGraph { fun chairshareFeatBuilder(): ChairshareFeatComponent.Builder } } /** * Marks a dependency as a singleton with the UDS dagger structure. You will get the same instance of the class annotated with this method * for the lifetime of the application. */ @Scope @Retention(AnnotationRetention.RUNTIME) annotation class AirSingleton /** * Allows the use of AssistedInject: https://airbnb.quip.com/8v0VAWSnofZv/Dagger-at-Airbnb#ZTOACAHsbQy. * You should never need to add anything to this module. */ @AssistedModule @Module(includes = [AssistedInject_AssistedModule::class]) @AppModuleDeclaration abstract class AssistedModule UDS