nalexn / clean-architecture-swiftui

SwiftUI sample app using Clean Architecture. Examples of working with CoreData persistence, networking, dependency injection, unit testing, and more.
MIT License
5.56k stars 672 forks source link

Question: clean architecture, boundaries and reactive frameworks #56

Open jrmybrault opened 3 years ago

jrmybrault commented 3 years ago

Hello !

I just discovered your work through this breakdown https://nalexn.github.io/uikit-switfui/ and followed my way up to here :). First of all thank you for this very well articulated article. While reading it, I was thinking "yes.. yes... oh yeah ! been there, see what you mean... totally agree and very-well phrased... this guy is on point !" I especially loved this part: "Nevertheless, there is a heck of a lot of business logic running on modern mobile apps, but that logic is different. It’s just more focused on the presentation rather than on the core rules the business runs on.

This means we need to do a better job at decoupling that presentation-related business logic from the specifics of the UI framework we’re using."

Still, one thing "surprised"/"puzzled" me a bit, regarding clean architecture. On one hand you seem very prone to raise a strict boundary between the presentation layer & the "domain/core layer" (even for simple use cases that fetch data from an abstract repo), pushing UIKit / SwiftUI specificities to the outer limits, where they belong. Basically, if it's a framework then I don't want it to be polluting the core of my app. Yet, you don't seem reluctant or bothered at all to "let" a reactive programming framework (be it an Apple "stamped" one like Combine or worse, a third-party one like Rx) going through pretty much everything, from data fetching to use-cases to presentation to ui. I'm sure this is a totally deliberate choice so I'd be glad if you can share some thoughts on that. For instance, did you ever considered preserving your core as strictly 100% vanilla Swift and plug the rest of the system through reactive adapters to this core ? Do you feel it's not worth it ? In a fictive scenario where Apple's Combine isn't quite "bridgeable" to Rx, because their respective philosophy differs a bit too much, this could be very damaging, don't you think ?

nalexn commented 3 years ago

Hey Jeremy!

That’s a great question! It is theoretically possible to abstract access to every framework used by the app, but most likely this will be overkill. I tend to make a decision whether to isolate a framework behind a wrapper / facade based on many factors.

  1. Trust to the vendor. I don’t trust certain Apple’s frameworks, some third party vendors like Facebook and Google, because I’ve had poor experience using their SDKs.

  2. Probability of framework replacement. There is a good chance for changing analytics framework “A” to another framework “B” if I need to. There is very little chance I’ll need to change RxSwift to ReactiveSwift or Combine, because they are functionally identical, so this will be very labor intensive without obvious benefit.

  3. Size of the subset of APIs I’ll be using. Imagine there Is a framework with 100 different functions, and your app is only using 5. It’s worthwhile adding a facade (GoF). If you plan to use 95 of those functions, your facade would become useless (if not harmful), as you end up repeating all the APIs.

Based on the above, I tend to go all-in with reactive frameworks, although I had a take on simplifying the APIs with this tiny lib: https://github.com/nalexn/minimalist

Regards, Alexey

On 3 May 2021, at 20:26, Jérémy Brault @.***> wrote:

 Hello !

I just discovered your work through this breakdown https://nalexn.github.io/uikit-switfui/ and followed my way up to here :). First of all thank you for this very well articulated article. While reading it, I was thinking "yes.. yes... oh yeah ! been there, see what you mean... totally agree and very-well phrased... this guy is on point !" I especially loved this part: "Nevertheless, there is a heck of a lot of business logic running on modern mobile apps, but that logic is different. It’s just more focused on the presentation rather than on the core rules the business runs on.

This means we need to do a better job at decoupling that presentation-related business logic from the specifics of the UI framework we’re using."

Still, one thing "surprised"/"puzzled" me a bit, regarding clean architecture. On one hand you seem very prone to raise a strict boundary between the presentation layer & the "domain/core layer" (even for simple use cases that fetch data from an abstract repo), pushing UIKit / SwiftUI specificities to the outer limits, where they belong. Basically, if it's a framework then I don't want it to be polluting the core of my app. Yet, you don't seem reluctant or bothered at all to "let" a reactive programming framework (be it an Apple "stamped" one like Combine or worse, a third-party one like Rx) going through pretty much everything, from data fetching to use-cases to presentation to ui. I'm sure this is a totally deliberate choice so I'd be glad if you can share some thoughts on that. For instance, did you ever considered preserving your core as strictly 100% vanilla Swift and plug the rest of the system through reactive adapters to this core ? Do you feel it's not worth it ? In a fictive scenario where Apple's Combine isn't quite "bridgeable" to Rx, because their respective philosophy differs a bit too much, this could be very damaging, don't you think ?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

jrmybrault commented 3 years ago

Hello Alexey,

thanks for your answer. That's a very interesting take indeed.

I guess I have an harder time trusting vendor, whoever they are, especially when their APIs tends to propagate like crazy. But in a sense, RxSwift has quite a number of years behind it now and will probably not disappear tomorrow, I'll give you that. Yet I'm pretty convinced that RxSwift will not survive Combine, in the long run. Obviously, the larger the codebase is, the larger that concern is. In a very large codebase where Rx is implied pretty much everywhere, maintenance and evolution are clearly at stake: updating to a newer version of the library becomes daunting, especially when some breaking changes are involved. In my experience, it tends to freeze until no longer possible, and you often ends up with huge migration/update project that will be boring yet risky, without clear time horizon.

Your point on the "surface" of the API is also very relevant. And that's precisely another reason I found common reactive programming libraries very "invasive", as their APIs are generally large, considering their "initial" purpose. You got the basics (observable / observer), the stuff you can't really ignore (disposable, bags, schedulers, ...) then a good deal of traits, all of that floating in an ocean of operators. Even when the library is narrower and more dedicated (as Combine is, compared to RxSwift), people often fall into adding some extensions library to "fill the gap". That's not mandatory of course, as using Rx traits is not mandatory, but the temptation is surely there to, as you say, "go all-in". When dealing with single event flows, which is frequent, I find Promise/Future libraries much more compelling and relevant, with a much more reasonable API surface.

Anyway, you clearly have a case for using a self-made, minimalistic, library. That's a nice thing to have in your bag 🙂 !

Regards,

Jérémy