Replies: 1 comment 3 replies
-
There's another possibility to consider that is related to the second style you described. You can create a computed property on extension DependencyValues {
var userItems: AsyncStream<[Item]> {
get async throws {
let items = try await self.itemRepository.allItems() // ✅
let ids = try await self.userManager.userItemIds() // ✅
// Do work in here to combine their latest values and output
// an array of the filtered items.
}
}
} And then in your reducer you can use it quite simply in a @Dependency(\.userItems) var userItems
…
return .run { send in
for await items in self.userItems {
await send(.itemsResponse(items))
}
} Alternatively, and pretty much equivalently, you can create a little helper that can be used from the reducer: func userItems() async throws -> AsyncStream<[Item]> {
@Dependency(\.itemRepository) var itemRepository
@Dependency(\.userManager) var userManager
let items = try await itemRepository.allItems() // ✅
let ids = try await userManager.userItemIds() // ✅
// Do work in here to combine their latest values and output
// an array of the filtered items.
} I personally think it will work out best if you keep your dependencies as simple, atomic units, and then perform as much of the business logic in the reducer as possible. But the above shows that you don't have to literally put everything in the reducer. You can still create little shareable units of logic that can be called to from the reducer. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Suppose we have a feature showing a list of a user's items.
There's a "repository" that provides all items (not just the user's):
and a UserManager providing the item ids:
Please note that both endpoints are modeled as "streams"; the available items might be updated on the server and a user can add/remove items on the web, so we'd like to be notified about these changes.
Is it advisable to create a dedicated
ItemFeatureClient
that will be the actual@Dependency
of our TCA-poweredItemFeature
? This client will depend onItemRepository
andUserManager
, combine the two streams, filter the results and the reducer will just consume them in.run {}
.But we could also add both
ItemRepository
andUserManager
as feature dependencies and do the filtering in the reducer, right? In that case we can easily mock both of them and even test the business logic of filtering, but how do we know when to stop? In our real-world scenario,ItemFeatureClient
depends on a few more components (not just two), and we feel that we'll be abusing the reducer if we move all our business logic there (remember, the user will always see the filtered results, there's no UI interaction).This question relates to another discussion about dependencies, but we're still not sure how to design our APIs (especially given that both
ItemRepository
andUserManager
will be used in different ways, from other features).Beta Was this translation helpful? Give feedback.
All reactions