StanfordSpezi / Spezi

Open-source framework for rapid development of modern, interoperable digital health applications.
https://swiftpackageindex.com/StanfordSpezi/Spezi/documentation
MIT License
130 stars 10 forks source link

Component communication #72

Closed Supereg closed 11 months ago

Supereg commented 11 months ago

Component communication

:recycle: Current situation & Problem

Currently, there is no easy way for a Component to access features or information provided by another Component.

I illustrate the usefulness using a real-world example we are currently facing with SpeziAccount. A AccountConfiguration is used by the user to configure the SpeziAccount subsystem which in turn does everything necessary to set up the request AccountServices to be accessible by the App's views. While one may just provide off-the-shelve AccountServices using the initializer of the configuration object, there might be instances where an third-party AccountService is controlled and configured by a Component. In such cases it should be possible for the user to place the Component in the Configuration closure as usual with the AccountConfiguration querying AccountService instances from all configured Components.

let config = Configuration(standard: TestAppStandard()>) {
    AccountConfiguration {
        SimpleAccountService()
    }

    FirebaseComponent() // a component preforming necessary initialization steps for its AccountService
}

:bulb: Proposed solution

In this PR we introduce the @Provide and @Collect property wrappers that can be used to pass around information or functionality between Components. The @Provide property wrapper is used to supply a single Value (or Value? if it e.g. only supplies it conditionally) or multiple values ([Value]) to be collected by other Components. Another Component can then use @Collect with a type of [Value] to collect all the provided items.

Here is a short code example:

class FirebaseComponent<ComponentStandard: Standard>: Component {
    @Provide private var accountService: any AccountService

    init() {
        // @Provide have to be initialized before the components configure() method is called
        self.accountService = FirebaseAccountService() // may also be initialize inline
    }

    func configure() {
        // do firebase initializations 
        accountService.configure() // as its a reference type, we can adjust state
    }
}

class AccountConfiguration<ComponentStandard: Standard>: Component {
    @Collect private var accountServices: [any AccountService]

    func configure() {
        // @Collect can be used within the configure method
        // ...
    }
}

While the example shows the usage of a reference type, this feature can also easily be used with value types (though then, no modifications post the collection phase are possible).

:gear: Release Notes

:heavy_plus_sign: Additional Information

Shared Repository Pattern

This PR additionally introduces the concept of the SharedRepository software pattern based on Buschmann et al. (Pattern-Oriented Software Architecture: A Pattern Language for Distributed Computing). We provide a variety of different types of KnowledgeSources which are stored in SharedRepository implementations. Each shared repository defines a SharedRepositoryAnchor it expects from an implementing KnowledgeSource.

This instantiation of the shared repository pattern replaces the previous TypedCollection implementation. Further, it will be of equal use for SpeziAccount (storing arbitrary account details). The current draft PR introduces a similar concept. This PR helps to generalize this software pattern and increase code reuse across the framework ecosystem.

Alternative Considered

As with the previous TypeCollection implementation, a SharedRepository allows for a collect(allOf:) query which is, e.g., internally used to query all Components that conform to LifecycleHandler. Instead of the proposed system, we could have created a property wrapper that just collects all components that conform to a given protocol. I decided against this approach considering the following points:

Related PRs

Testing

Test cases were adapted but still need to be extended for newly added code.

Reviewer Nudging

There are two separate parts to the PR:

Code of Conduct & Contributing Guidelines

By submitting creating this pull request, you agree to follow our Code of Conduct and Contributing Guidelines:

codecov[bot] commented 11 months ago

Codecov Report

Merging #72 (03e84f3) into main (162c932) will decrease coverage by 0.61%. The diff coverage is 92.24%.

Additional details and impacted files [![Impacted file tree graph](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72/graphs/tree.svg?width=650&height=150&src=pr&token=KHU2K1HTAM&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi)](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi) ```diff @@ Coverage Diff @@ ## main #72 +/- ## ========================================== - Coverage 89.45% 88.83% -0.61% ========================================== Files 30 44 +14 Lines 635 743 +108 ========================================== + Hits 568 660 +92 - Misses 67 83 +16 ``` | [Files Changed](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi) | Coverage Δ | | |---|---|---| | [Sources/Spezi/Adapter/Adapter.swift](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aS9BZGFwdGVyL0FkYXB0ZXIuc3dpZnQ=) | `0.00% <ø> (ø)` | | | [Sources/Spezi/Adapter/DataChange.swift](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aS9BZGFwdGVyL0RhdGFDaGFuZ2Uuc3dpZnQ=) | `100.00% <ø> (ø)` | | | [Sources/Spezi/Adapter/SingleValueAdapter.swift](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aS9BZGFwdGVyL1NpbmdsZVZhbHVlQWRhcHRlci5zd2lmdA==) | `100.00% <ø> (ø)` | | | [Sources/Spezi/Configuration/Component.swift](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aS9Db25maWd1cmF0aW9uL0NvbXBvbmVudC5zd2lmdA==) | `100.00% <ø> (ø)` | | | [Sources/Spezi/Configuration/Configuration.swift](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aS9Db25maWd1cmF0aW9uL0NvbmZpZ3VyYXRpb24uc3dpZnQ=) | `100.00% <ø> (ø)` | | | [Sources/Spezi/DataSource/DataSourceRegistry.swift](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aS9EYXRhU291cmNlL0RhdGFTb3VyY2VSZWdpc3RyeS5zd2lmdA==) | `50.00% <ø> (ø)` | | | [...Provider/DataStorageProvidersPropertyWrapper.swift](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aS9EYXRhU3RvcmFnZVByb3ZpZGVyL0RhdGFTdG9yYWdlUHJvdmlkZXJzUHJvcGVydHlXcmFwcGVyLnN3aWZ0) | `100.00% <ø> (ø)` | | | [...dule/Capabilities/Lifecycle/LifecycleHandler.swift](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aS9Nb2R1bGUvQ2FwYWJpbGl0aWVzL0xpZmVjeWNsZS9MaWZlY3ljbGVIYW5kbGVyLnN3aWZ0) | `100.00% <ø> (ø)` | | | [...s/ObservableObject/ObservableObjectComponent.swift](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aS9Nb2R1bGUvQ2FwYWJpbGl0aWVzL09ic2VydmFibGVPYmplY3QvT2JzZXJ2YWJsZU9iamVjdENvbXBvbmVudC5zd2lmdA==) | `86.37% <ø> (ø)` | | | [Sources/Spezi/Spezi/SpeziSceneDelegate.swift](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi#diff-U291cmNlcy9TcGV6aS9TcGV6aS9TcGV6aVNjZW5lRGVsZWdhdGUuc3dpZnQ=) | `83.34% <ø> (ø)` | | | ... and [25 more](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi) | | ------ [Continue to review full report in Codecov by Sentry](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi). > **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi) > `Δ = absolute (impact)`, `ø = not affected`, `? = missing data` > Powered by [Codecov](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi). Last update [162c932...03e84f3](https://app.codecov.io/gh/StanfordSpezi/Spezi/pull/72?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=StanfordSpezi).
Supereg commented 11 months ago

@PSchmiedmayer I addresses all the feedback. Wanted to re-request your review just to make sure 🚀

PSchmiedmayer commented 11 months ago

Thank you! I will have some time in the late afternoon to re-review the PR. If it is fine with you I can fix smaller issues that I might find myself and then merge the PR after that? Maybe I don't even have some feedback and could also directly merge it 👍