Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Factory between different targets in a package and app #213

Open
LucasVanDongen opened this issue Jul 7, 2024 · 3 comments
Open

Comments

@LucasVanDongen
Copy link

Use Case

I'm working on improving my build times and previews speed and reliability by turning all of my major features into Packages. The strategy is to hide every dependency behind a protocol, and then only export 3rd party dependencies when I'm returning the actual implementation, but not when I'm running Previews.

This is the structure:

let package = Package(
    name: "Navigation",
    platforms: [.iOS(.v17)],
    products: [
        .library(
            name: "Navigation",
            targets: [
                implementations,
                ui
            ]
        ),
        .library(name: "Previews", targets: [previews]) // needed to run my previews, should not be imported
    ],
    dependencies: [
        .package(
            url: "https://github.com/apple/swift-async-algorithms/",
            exact: "1.0.0"
        ),
        .package(
            name: "SharedUI",
            path: "../SharedUI"
        ),
        .package(
            url: "https://github.com/mapbox/mapbox-navigation-ios.git",
            from: "3.0.0"
        ),
        .package(
            url: "https://github.com/mapbox/search-ios.git",
            from: "2.0.0"
        )
    ],
    targets: [
        .target( // The actual implementations of my protocols, using third party dependencies
            name: implementations,
            dependencies: [
                .targetItem(
                    name: protocols,
                    condition: .none
                ),
                mapboxSearch,
                mapboxNavigationCore
            ],
            path: "Sources/Implementations"
        ),
        .target( // The protocols I expose for these implementations, not requiring 3rd party dependencies
            name: protocols,
            dependencies: [
                .targetItem(
                    name: model,
                    condition: .none
                )
            ],
            path: "Sources/Protocols"
        ),
        .target( // The Model implementations I share everywhere (requests, state, enums etcetera)
            name: model,
            path: "Sources/Model"
        ),
        .target( // The UI, only gets protocols injected, has no clue about any big dependency
            name: ui,
            dependencies: [
                .targetItem(name: protocols, condition: .none),
                sharedUI
            ],
            path: "Sources/UI"
        ),
        .target( // Mocks only know about the lightweight protocols and perhaps Models
            name: mocks,
            dependencies: [
                .targetItem(name: protocols, condition: .none)
            ],
            path: "Sources/Mocks"
        ),
        .target( // Specific target to put my previews in. Previews only use the UI, protocols and lightweight mocks
            name: previews,
            dependencies: [
                .targetItem(name: ui, condition: .none),
                .targetItem(name: protocols, condition: .none),
                .targetItem(name: mocks, condition: .none)
            ],
            path: "Sources/Previews"
        ),
        .testTarget(
            name: "NavigationTests",
            dependencies: [.targetItem(name: implementations, condition: .none)]
        )
    ]
)

This works great and I'm back to snappy Previews because the only thing I build is View / ViewModel code, some lightweight MockSpies (generated by Sourcery).

However I have no idea how to fit Factory into this. The ViewModels and Views expect registrations in Containers but I cannot give those yet because they're different between the App that is consuming my UI target and my Previews that inject the mocks instead.

So the UI relies on the App itself to register these dependencies (coming from the Implementations target) but the Previews module has its own registrations. The UI module doesn't understand this and fails to build.

Solution Direction

I'm looking for a way where I can "promise" the UI module that the dependencies will be set at run-time, in a compile-time safe way, by the target or app that consumes it. I tried messing around with protocols but couldn't get anything to work. The only thing I saw possible so far is injecting the dependencies Container manually.

@hmlongco
Copy link
Owner

hmlongco commented Jul 7, 2024

Covered quite a bit of this in the documentation. https://hmlongco.github.io/Factory/documentation/factory/modules

Especially the section on Separating Dependencies.

@LucasVanDongen
Copy link
Author

Thanks for the link!

I have been thinking a lot lately about this, I tried a bunch of things myself as well, but there seems to be no compile-safe way to guarantee that both sides of the fence implement a dependency, unless we start doing everything manually again.

@hmlongco
Copy link
Owner

That's why the recommendation for multi-module development is to make them optionals.

Or code the base dependency definition with a stub or nop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants