An architecture for Dagger that scales well to large codebases. Provided as a series of simple rules that humans and agents can follow (with examples).An architecture for Dagger that scales well to large codebases. Provided as a series of simple rules that humans and agents can follow (with examples).

How to Structure Dagger Components So Your Build System Doesn’t Hate You

This document was adapted from Dagger Directives for a monorepo that uses Bazel Build, and is provided for ease of use in other organizations.

Contents

This document is extensive, and while each directive is simple, the broader architecture they promote may be unclear; therefore, an end-to-end example is provided to aid comprehension, and the underlying architectural rationale is provided to link the individual directives to broader engineering principles. The sections are presented for ease of reference, with directives first; however, readers are encouraged to begin with whichever section they find most helpful.

Terminology

The following definitions apply throughout this document:

  • Component: An interface annotated with @Component
  • Module: A class or interface annotated with @Module
  • Scope: A custom annotation meta-annotated with @Scope

Component Structure and Scoping

Directives for how components are defined, scoped, and related to one another.

Practice: Library-Provided Components

Libraries and generic utilities should provide components that expose their functionality and declare their component dependencies instead of only providing raw classes/interfaces.

Positive Example: A Networking library provides a NetworkingComponent that exposes anOkHttpClient binding and depends on a CoroutinesComponent.

Negative Example: A Networking library that provides various interfaces and classes, but no component, and requires downstream consumers to define modules and components to wire them together.

This approach transforms Dagger components from details of the downstream application into details of upstream libraries. Instead of forcing consumers to understand a library's internal structure(and figure out how to instantiate objects), library authors provide complete, ready-to-use components that can be composed together and used to instantiate objects. This approach is analogous to plugging in a finished appliance instead of assembling a kit of parts: consumers just declare a dependency on the component (e.g. a fridge), supply the upstream components (e.g. electricity), and get the fully configured objects they need without ever seeing the wiring (e.g. cold drinks). This approach scales well, at the cost of more boilerplate.

Practice: Narrow Scoped Components

Components should export a minimal set of bindings, accept only the dependencies they require to operate (i.e. with @BindsInstance), and depend only on the components they require to operate.

Positive Example: A Feature component that depends only on Network and Database components, exposes only its public API (e.g. FeatureUi), and keeps its internal bindings hidden.

Negative Example: A Feature component that depends on a monolithic App component (which itself goes against the practice), exposes various bindings that could exist in isolation (e.g.FeatureUi, Clock, NetworkPorts and RpcBridge, IntegerUtil), and exposes its internal bindings.

This allows consumers to compose functionality with granular precision, reduces unnecessary configuration (i.e. passing instances/dependencies that are not used at runtime), and optimizes build times. This approach is consistent with the core tenets of the Interface Segregation Principle in that it ensures that downstream components can depend on the components they need, without being forced to depend on unnecessary components.

Practice: Naked Component Interfaces

Components should be defined as plain interfaces ("naked interfaces") without Dagger annotations, and then extended by annotated interfaces for production, testing, and other purposes. Downstream components should target the naked interfaces in their component dependencies instead of the annotated interfaces.

Example:

// Definition interface FooComponent { fun foo(): Foo } // Production Implementation @Component(modules = [FooModule::class]) interface ProdFooComponent : FooComponent // Testing Implementation @Component(modules = [FakeFooModule::class]) interface TestFooComponent : FooComponent { fun fakeFoo(): FakeFoo } @Component(dependencies = [FooComponent::class]) interface BarComponent { @Component.Builder interface Builder { fun consuming(fooComponent: FooComponent): Builder fun build(): BarComponent } }

This ensures Dagger code follows general engineering principles (separation of interface and implementation). While Dagger components are interfaces, the presence of a `@Component` annotation implicitly creates an associated implementation (the generated Dagger code); therefore, depending on an annotated component forces a dependency on its implementation (at the build system level), and implicitly forces test code to depend on production code. By separating them, consumers can depend on a pure interface without needing to include the Dagger implementation in their class path, thereby preventing leaky abstractions, optimising build times, and directly separating production and test code into discrete branches.

Standard: Custom Scope Required

Components must be bound to a custom Dagger scope.

Example:

@FooScope @Component interface ProdFooComponent : FooComponent { fun foo(): Foo }

Unscoped bindings can lead to subtle bugs where expensive objects are recreated or shared state is lost. Explicit lifecycle management ensures objects are retained only as long as needed, thereby preventing these issues.

Standard: Module Inclusion Restrictions

Components must only include modules defined within their own package or its subpackages; however, they must never include modules from a subpackage if another component is defined in an intervening package.

Example:

Given the following package structure:

src ├── a │ ├── AComponent │ ├── AModule │ ├── sub1 │ │ └── Sub1Module │ └── sub2 │ ├── Sub2Component │ └── sub3 │ └── Sub3Module └── b └── BModule

AComponent may include AModule (same package) and Sub1Module (subpackage with no intervening component), but not Sub3Module (intervening Sub2Component in a.sub2) or BModule (not a subpackage of a).

This enforces strict architectural layering and prevents dependency cycles (spaghetti code), thereby ensuring proper component boundaries and maintainability.

Practice: Dependencies Over Subcomponents

Component dependencies should be used instead of subcomponents.

Example: Foo depends on Bar via @Component(dependencies = [Bar::class]) rather than using@Subcomponent.

While subcomponents are a standard feature of Dagger, prohibiting them favors a flat composition-based component graph, thereby reducing cognitive load, allowing components to be tested in isolation, and creating a more scalable architecture.

Practice: Cross-Package Dependencies

Components may depend on components from any package.

Example: Foo in a.b can depend on Bar in x.y.z.

Allowing components to depend on each other regardless of location promotes reuse, thereby fostering high cohesion within packages.

Standard: Component Suffix

Components must include the suffix Component in their name.

Positive example: ConcurrencyComponent

Negative example: Concurrency

This clearly distinguishes the component interface from the functionality it provides and prevents naming collisions.

Standard: Scope Naming Convention

The name of the custom scope associated with a component must inherit the name of the component (minus "Component") with "Scope" appended.

Example: FooComponent is associated with FooScope.

Consistent naming allows contributors to immediately associate a scope with its component, thereby preventing conflicts and reducing split-attention effects.

Standard: Builder Naming

Component builders must be called `Builder`.

Example:

@Component interface FooComponent { @Component.Builder interface Builder { @BindsInstance fun binding(bar: Bar): Builder fun build(): FooComponent } }

Standardizing builder names allows engineers to predict the API surface of any component, thereby reducing the mental overhead when switching between components.

Standard: Binding Function Naming

Component builder functions that bind instances must be called `binding`; however, when bindings use qualifiers, the qualifier must be appended.

Example:

@Component interface ConcurrencyComponent { @Component.Builder interface Builder { // Unqualified @BindsInstance fun binding(bar: Bar): Builder // Qualified @BindsInstance fun bindingIo(@Io scope: CoroutineScope): Builder @BindsInstance fun bindingMain(@Main scope: CoroutineScope): Builder fun build(): ConcurrencyComponent } }

Explicit naming immediately clarifies the mechanism of injection (instance binding vs component dependency), thereby preventing collisions when binding multiple instances of the same type.

Standard: Dependency Function Naming

Component builder functions that set component dependencies must be called `consuming`.

Example:

@Component(dependencies = [Bar::class]) interface FooComponent { @Component.Builder interface Builder { fun consuming(bar: Bar): Builder fun build(): FooComponent } }

Distinct naming clearly separates structural dependencies (consuming) from runtime data (binding), thereby making the component's initialization logic self-documenting.

Standard: Provision Function Naming

Component provision functions must be named after the type they provide (in camelCase). However, when bindings use qualifiers, the qualifier must be appended to the function name.

Example:

@Component interface FooComponent { // Unqualified fun bar(): Bar // Qualified @Io fun genericIo(): Generic @Main fun genericMain(): Generic }

This ensures consistency and predictability in the component's public API.

Factory Functions

Requirements for the factory functions that instantiate components for ease of use.

Standard: Factory Function Required

Components must have an associated factory function that instantiates the component.

Example:

@Component(dependencies = [Quux::class]) interface FooComponent { // ... } fun fooComponent(quux: Quux = DaggerQuux.create(), qux: Qux): FooComponent = DaggerFooComponent.builder() .consuming(quux) .binding(qux) .build()

This integrates cleanly with Kotlin, thereby significantly reducing the amount of manual typing required to instantiate components.

Exception: Components that are file private may exclude the factory function (e.g. components defined in tests for consumption in the test only).

Standard: Default Component Dependencies

Factory functions must supply default arguments for parameters that represent component dependencies.

Example: fun fooComponent(quux: Quux = DaggerQuux.create(), ...)

Providing defaults for dependencies allows consumers to focus on the parameters that actually vary, thereby improving developer experience and reducing boilerplate.

Practice: Production Defaults

The default arguments for component dependency parameters in factory functions should be production components, even when the component being assembled is a test component.

Example: fun testFooComponent(quux: Quux = DaggerQuux.create(), ...)

This ensures tests exercise real production components and behaviours as much as possible, thereby reducing the risk of configuration drift between test and production environments.

Practice: Factory Function Location

Factory functions should be defined as top-level functions in the same file as the component.

Example: fooComponent() function in same file as FooComponent interface.

Co-locating the factory with the component improves discoverability.

Practice: Factory Function Naming

Factory function names should match the component, but in lower camel case.

Example: FooComponent component has fun fooComponent(...) factory function.

This ensures factory functions can be matched to components easily.

Practice: Default Non-Component Parameters

Factory functions should supply default arguments for parameters that do not represent component dependencies (where possible).

Example: fun fooComponent(config: Config = Config.DEFAULT, ...)

Sensible defaults allow consumers to only specify non-standard configuration when necessary, thereby reducing cognitive load.

Modules and Build Targets

Directives regarding Dagger modules and their placement in build targets.

Standard: Separate Module Targets

Modules must be defined in separate build targets to the objects they provide/bind.

Example: BarModule in separate build target from Baz implementation.

Separating implementation from interface/binding prevents changing an implementation from invalidating the cache of every consumer of the interface, thereby improving build performance.Additionally, it ensures consumers can depend on individual elements independently (crucial forHilt) and allows granular binding overrides in tests.

Standard: Dependency Interfaces

Modules must depend on interfaces rather than implementations.

Example: BarModule depends on Baz interface, not BazImpl.

This enforces consistency with the dependency inversion principle, thereby decoupling the module and its bindings from concrete implementations.

Testing Patterns

Patterns for defining components used in testing to ensure testability.

Standard: Test Component Extension

Test components must extend production components.

Example: interface TestFooComponent : FooComponent

Tests should operate on the same interface as production code (Liskov Substitution), thereby ensuring that the test environment accurately reflects production behavior.

Practice: Additional Test Bindings

Test components should export additional bindings.

Example: TestFooComponent component extends FooComponent and additionally exposes fun testHelper(): TestHelper.

Exposing test-specific bindings allows tests to inspect internal state or inject test doubles without compromising the public production API, thereby facilitating white-box testing where appropriate.

Rationale

The directives in this document work together to promote an architectural pattern for Dagger that follows foundational engineering best practices and principles, which in turn supports sustainable development and improves the contributor experience. The core principles are:

  • Interface Segregation Principle (ISP): Downstream consumers should be able to depend on the minimal API required for their task without being forced to consume/configure irrelevant objects. This reduces cognitive overhead for both maintainers and consumers, and lowers computational costs at build time and runtime. It's supported by directives such as the "Narrow Scoped Components" practice, which calls for small granular components instead of large God Objects, and the "Dependencies Over Subcomponents" practice, which encourages composition over inheritance.
  • Dependency Inversion Principle: High-level elements should not depend on low-level elements; instead, both should depend on abstractions. This reduces the complexity and scope of changes by allowing components to evolve independently and preventing unnecessary recompilation (in a complex build system such as Bazel). It's supported by the "Naked Component Interfaces" directive, which requires the use of interfaces rather than implementations, and the "Module Inclusion Restrictions" standard, which enforces strict architectural layering.
  • Abstraction and Encapsulation: Complex subsystems should expose simple, predictable interfaces that hide their internal complexity and configuration requirements. This allows maintainers and consumers to use and integrate components without deep understanding of the implementation. It's supported by the "Factory Function Required" standard, which encourages simple entry points, and "Default Component Dependencies", which provides sensible defaults to eliminate "Builder Hell".
  • Liskov Substitution Principle (LSP): Objects of a superclass must be replaceable with objects of its subclasses without breaking the application. This ensures test doubles can be seamlessly swapped in during tests, thereby improving testability without requiring changes to production code, and ensuring as much production code is tested as possible. It's supported by the "Test Component Extension" standard, which mandates that test components inherit from production component interfaces.
  • Compile-Time Safety (Poka-Yoke): The system is designed to prevent misconfiguration errors (i.e. "error-proofing"). By explicitly declaring component dependencies in the component interface, Dagger enforces their presence at compile time, and fails if they are missing. This gives consumers a clear, immediate signal of exactly what is missing or misconfigured, rather than failing obscurely at runtime. It's supported by the "Library-Provided Components" practice, which mandates fully declared dependencies, and the "Factory Function Required" standard, which mechanically ensures all requirements are satisfied effectively.

Overall, this architecture encourages and supports granular, maintainable components that can be evolved independently and composed together into complex structures. Components serve as both the public API for utilities, the integration system that ties elements together within utilities, and the composition system that combines utilities together. For upstream utility maintainers, this reduces boilerplate and reduces the risk of errors; for downstream utility consumers, this creates an unambiguous and self-documenting API that can be integrated without knowledge of implementation details; and for everyone, it distributes complexity across the codebase and promotes high cohesion(i.e. components defined nearest to the objects they expose). All together, this fosters sustainable development by reducing cognitive and computational load. \n The disadvantages of this approach and a strategy for mitigation are discussed in the[future work](#future-work) appendix.

End to End Example

The following example demonstrates a complete Dagger setup and usage that adheres to all the directives in this document. It features upstream (User) and downstream (Profile) components, separate modules for production and testing (including fake implementations), and strict separation of interface and implementation via naked component interfaces.

User Feature

Common elements:

/** Custom Scope */ @Scope @Retention(AnnotationRetention.RUNTIME) annotation class UserScope /** Domain Interface */ interface User /** Naked Component */ interface UserComponent { fun user(): User }

Production elements:

/** Real Implementation */ @UserScope class RealUser @Inject constructor() : User /** Production Module */ @Module interface UserModule { @Binds fun bind(impl: RealUser): User companion object { @Provides fun provideTimeout() = 5000L } } /** Production Component */ @UserScope @Component(modules = [UserModule::class]) interface ProdUserComponent : UserComponent { @Component.Builder interface Builder { fun build(): ProdUserComponent } } /** Production Factory Function */ fun userComponent(): UserComponent = DaggerProdUserComponent.builder().build()

Test elements:

/** Fake Implementation */ @UserScope class FakeUser @Inject constructor() : User /** Fake Module */ @Module interface FakeUserModule { @Binds fun bind(impl: FakeUser): User } /** Test Component */ @UserScope @Component(modules = [FakeUserModule::class]) interface TestUserComponent : UserComponent { fun fakeUser(): FakeUser @Component.Builder interface Builder { fun build(): TestUserComponent } } /** Test Factory Function */ fun testUserComponent(): TestUserComponent = DaggerTestUserComponent.builder().build()

Profile Feature

Common elements:

/** Custom Scope */ @Scope @Retention(AnnotationRetention.RUNTIME) annotation class ProfileScope /** Domain Interface */ interface Profile /** Naked Component */ interface ProfileComponent { fun profile(): Profile }

Production elements:

** Real Implementation */ @ProfileScope class RealProfile @Inject constructor( val user: User, private val id: ProfileId ) : Profile { data class ProfileId(val id: String) } /** Production Module */ @Module interface ProfileModule { @Binds fun bind(impl: RealProfile): Profile } /** Production Component */ @ProfileScope @Component(dependencies = [UserComponent::class], modules = [ProfileModule::class]) interface ProdProfileComponent : ProfileComponent { @Component.Builder interface Builder { fun consuming(user: UserComponent): Builder @BindsInstance fun binding(id: ProfileId): Builder fun build(): ProdProfileComponent } } /** Production Factory Function */ fun profileComponent( user: UserComponent = userComponent(), id: ProfileId = ProfileId("prod-id") ): ProfileComponent = DaggerProdProfileComponent.builder().consuming(user).binding(id).build()

Test elements:

/** Test Component */ @ProfileScope @Component(dependencies = [UserComponent::class], modules = [ProfileModule::class]) interface TestProfileComponent : ProfileComponent { @Component.Builder interface Builder { fun consuming(user: UserComponent): Builder @BindsInstance fun binding(id: ProfileId): Builder fun build(): TestProfileComponent } } /** Test Factory Function */ fun testProfileComponent( user: UserComponent = userComponent(), id: ProfileId = ProfileId("test-id") ): TestProfileComponent = DaggerTestProfileComponent.builder().consuming(user).binding(id).build()

Usage

Example of production component used in production application:

class Application { fun main() { // Automatically uses production implementations (RealUser, RealProfile) val profile = profileComponent().profile() // ... } }

Example of production profile component used with test user component in a test:

@Test fun testProfileWithFakeUser() { // 1. Setup: Create the upstream test component (provides FakeUser) val fakeUserComponent = testUserComponent() val fakeUser = fakeUserComponent.fakeUser() // 2. Act: Inject it into the downstream test component val prodProfileComponent = profileComponent(user = fakeUserComponent) val profile = prodProfileComponent.profile() // 3. Assert: Verify integration assertThat(profile.user).isEqualTo(fakeUser) }

Future Work

The main disadvantage of the pattern this document encodes is the need for a final downstreamassembly of components, which can become boilerplate heavy in deep graphs. For example:

fun main() { // Level 1: Base component val core = coreComponent() // Level 2: Depends on Core val auth = authComponent(core = core) val data = dataComponent(core = core) // Level 3: Depends on Auth, Data, AND Core val feature = featureComponent(auth = auth, data = data, core = core) // Level 4: Depends on Feature, Auth, AND Core val app = appComponent(feature = feature, auth = auth, core = core) }

A tool to reduce this boilerplate has been designed, and implementation is tracked by this issue.

Piyasa Fırsatı
Threshold Logosu
Threshold Fiyatı(T)
$0.008629
$0.008629$0.008629
+0.10%
USD
Threshold (T) Canlı Fiyat Grafiği
Sorumluluk Reddi: Bu sitede yeniden yayınlanan makaleler, halka açık platformlardan alınmıştır ve yalnızca bilgilendirme amaçlıdır. MEXC'nin görüşlerini yansıtmayabilir. Tüm hakları telif sahiplerine aittir. Herhangi bir içeriğin üçüncü taraf haklarını ihlal ettiğini düşünüyorsanız, kaldırılması için lütfen service@support.mexc.com ile iletişime geçin. MEXC, içeriğin doğruluğu, eksiksizliği veya güncelliği konusunda hiçbir garanti vermez ve sağlanan bilgilere dayalı olarak alınan herhangi bir eylemden sorumlu değildir. İçerik, finansal, yasal veya diğer profesyonel tavsiye niteliğinde değildir ve MEXC tarafından bir tavsiye veya onay olarak değerlendirilmemelidir.

Ayrıca Şunları da Beğenebilirsiniz

Forward Industries zet $4 miljard in om Solana bezit uit te breiden

Forward Industries zet $4 miljard in om Solana bezit uit te breiden

Forward Industries gooit het roer om met een flinke financiële zet: het bedrijf lanceert een zogeheten “At The Market” aandelenprogramma van maar liefst $4 miljard. Het programma geeft het bedrijf flexibiliteit om op elk gewenst moment aandelen te verkopen, wat vooral handig is voor het uitbreiden van hun Solana treasury... Het bericht Forward Industries zet $4 miljard in om Solana bezit uit te breiden verscheen het eerst op Blockchain Stories.
Paylaş
Coinstats2025/09/18 01:31
Today’s NYT Pips Hints And Solutions For Thursday, September 18th

Today’s NYT Pips Hints And Solutions For Thursday, September 18th

The post Today’s NYT Pips Hints And Solutions For Thursday, September 18th appeared on BitcoinEthereumNews.com. It’s Thursday and I am incredibly sore and tired after really hitting the weights and the yoga mat hard this week. Sore is good! It takes pain to reduce pain, or at least that’s my experience with exercise. We must exercise our minds as well, and what better way to do that than with a fun puzzle game about placing dominoes in the correct tiles. Come along, my Pipsqueaks, let’s solve today’s Pips! Looking for Wednesday’s Pips? Read our guide right here. How To Play Pips In Pips, you have a grid of multicolored boxes. Each colored area represents a different “condition” that you have to achieve. You have a select number of dominoes that you have to spend filling in the grid. You must use every domino and achieve every condition properly to win. There are Easy, Medium and Difficult tiers. Here’s an example of a difficult tier Pips: Pips example Screenshot: Erik Kain As you can see, the grid has a bunch of symbols and numbers with each color. On the far left, the three purple squares must not equal one another (hence the equal sign crossed out). The two pink squares next to that must equal a total of 0. The zig-zagging blue squares all must equal one another. You click on dominoes to rotate them, and will need to since they have to be rotated to fit where they belong. Not shown on this grid are other conditions, such as “less than” or “greater than.” If there are multiple tiles with > or < signs, the total of those tiles must be greater or less than the listed number. It varies by grid. Blank spaces can have anything. The various possible conditions are: = All pips must equal one another in this group. ≠ All pips…
Paylaş
BitcoinEthereumNews2025/09/18 08:59
Bitcoin ETFs Record Strongest Inflows Since July, Push Holdings to New High

Bitcoin ETFs Record Strongest Inflows Since July, Push Holdings to New High

The post Bitcoin ETFs Record Strongest Inflows Since July, Push Holdings to New High appeared on BitcoinEthereumNews.com. In brief Bitcoin ETPs saw a net inflow of 20,685 BTC last week, driven mostly by U.S. ETFs. The recent uptick in investor risk appetite is driven by rate cut expectations and new crypto IPOs. Despite institutional demand outpacing new Bitcoin supply, realized and implied volatility remain historically low. Bitcoin exchange-traded products globally logged net inflows of 20,685 BTC last week, the strongest weekly intake since July 22, according to digital assets firm K33 Research. The renewed momentum lifted U.S. spot bitcoin ETFs’ combined holdings to 1.32 million BTC, surpassing the previous peak set on July 30. U.S. Bitcoin ETF products contributed nearly 97% of last week’s 20,685 BTC ETP inflows, highlighting the surge in demand ahead of the FOMC meeting.  Bitcoin ETF inflows “tend to be one of the key determinants of Bitcoin’s performance,” André Dragosch, head of research for Europe at Bitwise Investments, told Decrypt, adding that the “percentage share of Bitcoin’s performance explained by changes in ETP flows” has reached a new all-time high. Compared with Ethereum ETF flows, “there appears to be a ‘re-rotation’ from Ethereum back to Bitcoin in terms of investor flows,” Dragosch said, citing their data. “Over the past week, flows into Bitcoin ETFs have surpassed new supply growth by a factor of 8.93 times, a key tailwind for Bitcoin’s recent performance.”  Analysts at K33 agree, writing that flows have been a key driver of bitcoin’s strength since ETF approvals earlier last year, and the latest surge signals an acceleration in demand that could underpin further price support. In the last 30 days, investors accumulated roughly 22,853 BTC via various products, outpacing the new supply of 14,056 BTC. This rising risk appetite for Bitcoin has supported the recent recovery, Bitwise noted in its Monday report. Fidelity’s FBTC product accounted for a substantial…
Paylaş
BitcoinEthereumNews2025/09/18 10:19